Dockerが生まれる前の現実的な問題
チームで働き始めた頃のことを今でも覚えている。ローカルでアプリをビルドしてサーバーにプッシュすると、結果は即座にクラッシュ。原因は?サーバーはPython 3.8を使っていたが、自分のマシンはPython 3.11だった。ライブラリのバージョンが違い、環境変数が不足し、設定がずれていた…デプロイのたびに「運頼み」の状態だった。
Dockerはまさにこの問題を解決するために生まれた:コード・依存関係・設定のすべてを含むアプリケーション全体を、どこでも実行できるひとつの単位にパッケージ化する。「自分のマシンでは動くのに」という言い訳はもう不要だ。
DockerはVirtual Machine(VM)ではない。VMは独立したOSを持つ完全な仮想マシンを作り、数GBもの容量を消費する。コンテナはホストOSのカーネルを共有し、プロセスレベルでのみ分離する——数秒で起動し、RAMの消費はGBではなくMB単位だ。
Ubuntu/LinuxへのDockerインストール
現在ほとんどのVPSはUbuntuで動いているので、Ubuntu 22.04/24.04向けに手順を説明する。Debianでも手順はほぼ同じだ。
ステップ1:古いバージョンの削除(存在する場合)
sudo apt remove docker docker-engine docker.io containerd runc
ステップ2:公式Dockerリポジトリの追加
sudo apt update
sudo apt install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
ステップ3:Docker Engineのインストール
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
ステップ4:sudo不要でDockerを実行できるようにする
sudo usermod -aG docker $USER
newgrp docker
グループ変更を有効にするため、一度ログアウトしてログインし直す。動作確認:
docker --version
# Docker version 26.x.x, build xxxxxxx
docker run hello-world
“Hello from Docker!” というメッセージが表示されれば、インストール成功だ。
しっかり理解すべき重要な概念
最初のコマンドを打つ前に、この4つの概念を正しく理解するのに5分使おう——後でエラーを追いかけるより遥かに効率がいい。ImageとContainerを混同して誤って削除したり、間違った場所でrestartして、問題がどこにあるかわからないまま半日デバッグし続ける人を何人も見てきた。
Image:不変の設計図
ImageはOSのベース・ライブラリ・コード・環境変数など、アプリケーションの実行に必要なすべてを含むread-onlyファイルだ。WindowsをインストールするためのISOファイルのようなものだと考えよう——ISOは編集せず、実行可能なバージョンを作るためだけに使う。
# Docker Hubからnginxイメージをダウンロード
docker pull nginx:1.25
# 現在のイメージ一覧を表示
docker images
# イメージを削除
docker rmi nginx:1.25
Container:ImageからのRunning Instance
ContainerはImageを「実行」したときの具体的なインスタンスだ。ISOファイルを10台の異なる仮想マシンにインストールできるように——ひとつのImageからいくつでもContainerを作れて、それぞれが互いに、そしてホストOSとも完全に分離されている。
# nginxコンテナを起動し、ホストの8080ポートをコンテナの80ポートにマッピング
docker run -d -p 8080:80 --name my-nginx nginx:1.25
# 実行中のコンテナを表示
docker ps
# すべてのコンテナを表示(停止中のものを含む)
docker ps -a
# コンテナを停止・削除
docker stop my-nginx
docker rm my-nginx
Volume:永続的なデータ保存
Containerは本質的にephemeral(一時的)だ——Containerを削除すると、内部のデータもすべて消える。Volumeはその解決策で、ホストのディレクトリをコンテナ内にマウントすることで、Containerのライフサイクルとは独立してデータを保持する。
# ホストの/data/mysqlをコンテナ内の/var/lib/mysqlにマウント
docker run -d \
-v /data/mysql:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
--name mysql-db \
mysql:8.0
データはホストの/data/mysqlに保存される——Containerを削除して再作成してもデータは失われない。
Network:Container間の通信
デフォルトでは各コンテナはbridgeネットワーク内に固有のIPを持つが、このIPは再起動のたびに変わる。ホスト名による接続の方がはるかに安定している。同じスタック内のContainerが名前で互いを見つけられるよう、カスタムネットワークを作成しよう:
# ネットワークを作成
docker network create my-app-net
# そのネットワーク内でコンテナを起動
docker run -d --network my-app-net --name db mysql:8.0
docker run -d --network my-app-net --name web my-webapp
# "web"コンテナはホスト名"db"で"db"に接続できる——IPを知る必要なし
最初のDockerfileを書く
DockerfileはDockerにImageをゼロから作成する手順を教えるテキストファイルだ——ファイル内の各命令が1つのレイヤーに対応する。Python Flaskアプリの実例を見てみよう:
FROM python:3.12-slim
WORKDIR /app
# requirementsを先にCOPY——Dockerレイヤーキャッシュを活用するため
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
ビルドして動作確認:
# 現在のディレクトリのDockerfileからイメージをビルド
docker build -t my-flask-app:v1 .
# 動作確認
docker run -d -p 5000:5000 my-flask-app:v1
小ワザ:常にCOPY requirements.txtとRUN pip installをコードのCOPYより前に実行しよう。Dockerは順番通りにレイヤーをキャッシュする——コードが変わってもrequirementsが変わらなければ、ライブラリのインストールはキャッシュを使うのでビルドが大幅に速くなる。
Containerの確認とモニタリング
デプロイが終わったら、本当の仕事が始まる。Containerは静かにクラッシュしたり、RAMをリークしたり、気づかないうちにエラーが発生することがある——だから最初からこれらの基本コマンドを押さえておこう。
Containerのログ確認
# コンテナのログを表示
docker logs my-nginx
# リアルタイムでログを追跡(tail -fのように)
docker logs -f my-nginx
# 最後の50行のみ表示
docker logs --tail 50 my-nginx
リソース使用量の監視
# CPU/RAM使用量をリアルタイムで表示
docker stats
# フォローなしのスナップショット
docker stats --no-stream
30台以上のコンテナを動かす本番クラスターでは、docker statsを使って異常なリソースを「食い荒らす」コンテナを発見したことがある。Dockerfileを分析して最適化し——multi-stage buildを適用して不要なレイヤーを削減——サーバーを追加することなくリソース使用量を40%削減できた。PrometheusやGrafanaのようなモニタリングツールも優秀だが、docker statsはDockerをインストールした直後から使えて、追加セットアップは不要だ。
Containerの内部でデバッグする
# 実行中のコンテナでbashシェルを開く
docker exec -it my-nginx bash
# コンテナ内で単一コマンドを実行
docker exec my-nginx nginx -t
# コンテナの詳細情報を表示
docker inspect my-nginx
定期的なリソースクリーンアップ
しばらく使っていると、古いイメージ・停止したコンテナ・未使用のVolumeが蓄積してディスクを圧迫する。素早くクリーンアップするコマンド:
# 未使用リソースを削除(停止したコンテナ、dangling image、未使用ネットワーク)
docker system prune
# -aを追加するとどのコンテナにも使われていないイメージも削除
docker system prune -a
# Dockerが占有しているディスク容量を確認
docker system df
基礎を習得した後の次のステップ
上記のコマンドを習得したら?次に毎日使うことになるのはDocker Composeだ。さまざまなフラグを伴う5〜6回のdocker runコマンドを打つ代わりに、スタック全体をYAMLファイルで定義してdocker compose up -dだけでOK:
services:
web:
build: .
ports:
- "5000:5000"
depends_on:
- db
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: secret
volumes:
pgdata:
docker compose up -d
docker compose logs -f
docker compose down
Docker Composeに慣れてくると、Docker Swarm・Kubernetes・CI/CDパイプラインの自動化への道は思ったより近い——なぜなら、すべてが今学んだ4つの概念Image・Container・Volume・Networkを再利用するからだ。
