なぜDockerを使うのか
「自分のマシンでは動くのに、本番では動かない」——開発者なら一度は経験したことがあるだろう。
Dockerは、アプリケーションとその依存関係をコンテナという隔離された環境にパッケージングする技術だ。コンテナはどの環境でも同じように動作するため、開発・テスト・本番の差異が消える。
基本概念
イメージとコンテナ
| 概念 | 説明 | 例え |
|---|---|---|
| イメージ | アプリの設計図(読み取り専用) | レシピ |
| コンテナ | イメージから作られた実行環境 | レシピから作った料理 |
| Dockerfile | イメージの作り方を記述したファイル | レシピの書き方 |
| レジストリ | イメージの保管場所 | レシピ本の棚 |
コンテナ vs 仮想マシン
| 項目 | コンテナ | 仮想マシン |
|---|---|---|
| 起動時間 | 秒単位 | 分単位 |
| サイズ | MB単位 | GB単位 |
| オーバーヘッド | 極小 | 大きい |
| 分離レベル | プロセスレベル | OSレベル |
| 用途 | アプリケーション配布 | 完全なOS環境 |
コンテナはホストOSのカーネルを共有するため、仮想マシンに比べて圧倒的に軽い。
Dockerfileの書き方
Node.jsアプリケーションの例
# ベースイメージ
FROM node:24-alpine
# 作業ディレクトリ
WORKDIR /app
# 依存関係のインストール(キャッシュ最適化)
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
# ソースコードのコピー
COPY . .
# ビルド
RUN pnpm run build
# ポート公開
EXPOSE 3000
# 起動コマンド
CMD ["node", "dist/index.js"]
Dockerfileのベストプラクティス
- 軽量ベースイメージを使う:
node:24ではなくnode:24-alpine(900MB → 150MB) - レイヤーキャッシュを活用:
package.jsonを先にコピーし、依存関係のインストールをキャッシュ - マルチステージビルド: ビルド用と実行用のステージを分離してイメージサイズを削減
- .dockerignore を設定:
node_modules、.git、distをコピー対象から除外
マルチステージビルド
# ビルドステージ
FROM node:24-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build
# 実行ステージ
FROM node:24-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]
ビルドに必要なツールが最終イメージに含まれないため、イメージサイズが大幅に削減される。
よく使うDockerコマンド
イメージ操作
| コマンド | 用途 |
|---|---|
docker build -t myapp . | Dockerfileからイメージをビルド |
docker images | ローカルのイメージ一覧 |
docker rmi myapp | イメージを削除 |
docker pull nginx:latest | レジストリからイメージを取得 |
コンテナ操作
| コマンド | 用途 |
|---|---|
docker run -d -p 3000:3000 myapp | コンテナをバックグラウンドで起動 |
docker ps | 実行中のコンテナ一覧 |
docker logs <container> | ログを表示 |
docker exec -it <container> sh | コンテナ内でシェルを起動 |
docker stop <container> | コンテナを停止 |
クリーンアップ
# 停止中のコンテナ、未使用イメージ、未使用ネットワークを一括削除
docker system prune
# ボリュームも含めて削除(データも消える)
docker system prune --volumes
Docker Composeで複数サービスを管理
実際のアプリケーションでは、Webサーバー・データベース・キャッシュなど複数のサービスを組み合わせる。Docker Composeはこれを1つのファイルで定義する。
compose.yaml の例
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/myapp
depends_on:
db:
condition: service_healthy
db:
image: postgres:17-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: myapp
volumes:
- db-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:
db-data:
Composeコマンド
| コマンド | 用途 |
|---|---|
docker compose up -d | 全サービスを起動 |
docker compose down | 全サービスを停止・削除 |
docker compose logs -f app | 特定サービスのログを追跡 |
docker compose exec app sh | サービス内でコマンド実行 |
docker compose build | イメージを再ビルド |
開発環境でのDocker活用パターン
パターン1: ローカル開発環境の統一
チーム全員が同じ開発環境を使えるようにする。
# compose.dev.yaml
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/app # ソースコードをマウント
- /app/node_modules # node_modulesはコンテナ内のものを使用
ports:
- "3000:3000"
command: pnpm dev
ホストのソースコードをボリュームマウントすることで、ホットリロードが効く。
パターン2: テスト環境の構築
CI/CDでテスト用のデータベースを一時的に立ち上げる。
services:
test:
build: .
command: pnpm test
environment:
- DATABASE_URL=postgresql://test:test@db:5432/testdb
depends_on:
db:
condition: service_healthy
db:
image: postgres:17-alpine
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
パターン3: 本番環境の再現
本番と同じ構成をローカルで再現し、デプロイ前に動作確認する。
セキュリティのポイント
- rootで実行しない: Dockerfileに
USER nodeを追加し、非rootユーザーで実行 - シークレットをイメージに含めない: 環境変数やDocker Secretsを使う
- ベースイメージを定期更新: セキュリティパッチを反映
- 不要なパッケージを入れない: Alpine系イメージを使い、最小構成を維持
FROM node:24-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
USER appuser
CMD ["node", "dist/index.js"]
まとめ
Dockerは「環境差異の排除」という一点だけでも導入する価値がある。
最初の一歩は、既存プロジェクトにDockerfileを1つ追加すること。完璧な構成を目指す必要はない。docker build と docker run ができれば、チームメンバーの環境構築は docker compose up の一言で済むようになる。
覚えるべきコマンドは5つだけ: build、run、ps、logs、compose up。これだけで日常の開発は十分回る。