開発者のためのDocker入門:コンテナ、Compose、実践的なワークフロー
Dockerの概念、Dockerfileのベストプラクティス、Docker Composeを使ったマルチサービス開発環境の構築に関する実践的なガイドです。
「自分のマシンでは動くのに」という言葉は、数えきれないほどのデプロイ失敗を生み出してきました。Dockerはアプリケーションとそのすべての依存関係をコンテナにパッケージ化することでこの問題を解決します。コンテナとは、ラップトップから本番環境まで、どこでも同じように動作する隔離された再現可能な環境です。
コンテナと仮想マシンの違い
| 仮想マシン | コンテナ | |
|---|---|---|
| 分離レベル | OS全体 | プロセスレベル |
| 起動時間 | 数分 | 数秒 |
| サイズ | GB単位 | MB単位 |
| オーバーヘッド | 高い(ハイパーバイザー) | ほぼゼロ |
| ユースケース | OS完全分離 | アプリのパッケージ化 |
コンテナはホストのカーネルを共有しつつ、ファイルシステム・プロセス・ネットワークを分離します。これがコンテナが非常に軽量である理由です。
Dockerの基本概念
イメージ — コンテナの読み取り専用の設計図。クラス定義のようなものです。
コンテナ — イメージの実行中のインスタンス。クラスから生成されたオブジェクトのようなものです。
レジストリ — イメージの保存・配布システム。Docker Hubがデフォルトの公開レジストリで、GitHub Container RegistryやAWS ECRもよく使われます。
ボリューム — コンテナの再起動後も保持される永続ストレージ。ボリュームなしでコンテナ内に書き込まれたデータは、コンテナ停止時に失われます。
良いDockerfileの書き方
# 特定のバージョンを使用 — 本番環境では"latest"は避ける
FROM node:20-alpine
# 作業ディレクトリを設定
WORKDIR /app
# 依存ファイルを先にコピー(レイヤーキャッシュを活用)
COPY package.json package-lock.json ./
RUN npm ci --only=production
# アプリケーションコードをコピー
COPY . .
# アプリをビルド
RUN npm run build
# セキュリティのため非rootユーザーで実行
USER node
# ポートを明示(公開はしない)
EXPOSE 3000
# 適切なシグナル処理のためexec形式を使用
CMD ["node", "server.js"]
レイヤーキャッシュ:高速ビルドの鍵
Dockerfileの各命令はレイヤーを作成します。Dockerはレイヤーをキャッシュし、あるレイヤーやその前のレイヤーに変更がなければ、キャッシュを再利用します。
アプリケーションコードより先に依存ファイルをコピーしましょう。 依存関係はコードに比べてはるかに変更頻度が低いです。すべてを一度にコピーすると、index.jsの1行の変更で依存関係インストールのキャッシュが無効化され、npm installが毎回フルで実行されてしまいます。
マルチステージビルド
マルチステージビルドを使って本番イメージをスリムに保ちましょう:
# ステージ1:ビルド
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ステージ2:本番(開発依存なし、ソースコードなし)
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]
最終イメージにはビルド成果物のみが含まれ、ソースコード・テストファイル・開発依存関係は含まれません。
ローカル開発のためのDocker Compose
Docker Composeはマルチコンテナアプリケーションを orchestrate します。1つのdocker-compose.ymlでローカルスタック全体を定義できます:
version: "3.9"
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/myapp
depends_on:
db:
condition: service_healthy
volumes:
- .:/app
- /app/node_modules
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres_data:
Docker Compose Generatorを使えば、スタックのひな形を簡単に生成できます。使用するサービスを選択するだけで、すぐに使えるdocker-compose.ymlが数秒で手に入ります。
Composeの主要コマンド
# すべてのサービスをバックグラウンドで起動
docker compose up -d
# ログを表示(フォローモード)
docker compose logs -f app
# 実行中のコンテナ内でコマンドを実行
docker compose exec app sh
# コンテナを停止・削除(ボリュームは保持)
docker compose down
# コンテナとボリュームを停止・削除
docker compose down -v
# Dockerfile変更後にイメージを再ビルド
docker compose up -d --build
環境変数とシークレット
DockerfileやComposeファイルに認証情報をハードコードしないでください。.envファイルを使いましょう:
# .env(.gitignoreに追加!)
DATABASE_URL=postgresql://user:pass@db:5432/myapp
REDIS_URL=redis://redis:6379
JWT_SECRET=your-super-secret-key
Composeはプロジェクトディレクトリから.envを自動的に読み込みます。YAML内では${変数名}の形式で変数を参照します。
Env Generatorを使うと、適切なデフォルト値を持つ.envファイルや、チーム共有用の.env.exampleテンプレートをすばやく生成できます。
Dockerのネットワーキング
Composeはスタック用のデフォルトネットワークを作成します。サービスはサービス名をホスト名として互いに通信できます。アプリがlocalhostではなくdbをデータベースホストとして使用するのはこのためです。
app → "db:5432" に接続
app → "redis:6379" に接続
ports:で明示的にマッピングされたポートのみが、ホストマシンからアクセス可能になります。
ヘルスチェックと起動順序
depends_onはコンテナが起動するまで待つだけで、準備完了になるまでは待ちません。データベースコンテナは数秒で起動しますが、Postgresが接続を受け付けるまでにさらに数秒かかることがあります。healthcheckとcondition: service_healthyを組み合わせて、サービスが実際に準備完了になるまで待つようにしましょう。
本番環境での考慮事項
- 特定のイメージタグを使用する —
node:latestではなくnode:20.11.1-alpineのように指定する。 - イメージの脆弱性をスキャンする —
docker scout cves myapp:latest。 - リソース制限を設定する — 1つのコンテナが他のリソースを奪わないようにする。
- 可能な限り読み取り専用ファイルシステムを使用する — Composeで
read_only: trueを設定。 - rootとして実行しない —
USER nodeや同等の設定を追加する。 - Nginxをリバースプロキシとして設定する — Nginx Config Generatorを使って実績のある設定を取得できます。
クイックリファレンス
# イメージ
docker images # ローカルイメージの一覧
docker pull nginx:alpine # イメージのダウンロード
docker rmi myimage # イメージの削除
# コンテナ
docker ps # 実行中のコンテナ
docker ps -a # すべてのコンテナ
docker stop <id> # グレースフルに停止
docker rm <id> # 停止済みコンテナを削除
docker logs <id> -f # ログをストリーミング
# クリーンアップ
docker system prune # 未使用のすべてを削除
docker volume prune # 未使用のボリュームを削除
Dockerは環境起因のバグを根本から解消し、新しい開発者のオンボーディングを驚くほど簡単にします。ここで基本をマスターすれば、Kubernetes・CI/CDパイプライン・クラウドデプロイメントへと進む確かな基礎が身につきます。