開発者のためのDocker: コンテナ、イメージ、Compose

· 12分で読む

目次

Dockerを理解する: なぜ重要なのか

Dockerは、開発者がアプリケーションを構築、配布、実行する方法を根本的に変えました。Docker以前は、開発環境のセットアップは依存関係の競合、バージョンの不一致、そして悪名高い「私のマシンでは動く」問題の悪夢でした。

Dockerは、アプリケーションが実行に必要なすべてのもの(コード、ランタイム、システムツール、ライブラリ、設定)をコンテナと呼ばれる標準化されたユニットにパッケージ化することで、この問題を解決します。このコンテナは、あなたのノートパソコン、同僚のマシン、本番サーバーで同じように動作します。

メリットは即座に実感できます:

開発者にとって、Dockerは完全なアプリケーションスタック(Webサーバー、データベース、キャッシュ、メッセージキュー)を1つのコマンドで起動できることを意味します。PostgreSQLのインストールやRedis設定の問題のデバッグに何時間も費やす必要はもうありません。

コアコンセプトの解説

実践的な使用に入る前に、Dockerのコアコンセプトを理解することが不可欠です。これらの構成要素が連携してDockerエコシステムを作り上げています。

コンセプト それは何か 例え 主な特徴
イメージ アプリと依存関係を含む読み取り専用テンプレート OOPのクラス定義 不変、レイヤー化、共有可能
コンテナ イメージの実行中のインスタンス オブジェクト(クラスのインスタンス) 分離、一時的、ステートレス
Dockerfile イメージをビルドするための指示 レシピまたは設計図 テキストファイル、バージョン管理
ボリューム コンテナ外の永続ストレージ 外付けハードドライブ コンテナ削除後も存続
ネットワーク コンテナ間の通信 ローカルエリアネットワーク(LAN) 分離、設定可能、安全
レジストリ イメージのストレージ(Docker Hub) コンテナ用のnpm/PyPI パブリックまたはプライベート、バージョン管理

イメージ vs コンテナ: 重要な違い

ここで多くの初心者が混乱します。イメージは静的なスナップショットです。凍結されたテンプレートと考えてください。コンテナは、そのイメージを実行したときに得られるもので、実行中のプロセスです。

1つのイメージから無制限のコンテナを作成できます。1つのクラスから複数のオブジェクトを作成できるのと同じです。各コンテナは、同じイメージに基づいていても、他のコンテナから分離されています。

プロのヒント: イメージはレイヤーで構築されます。Dockerfileの各命令が新しいレイヤーを作成します。Dockerはこれらのレイヤーをキャッシュするため、イメージの再ビルドは変更されたレイヤーのみを再ビルドします。これによりビルドが非常に高速になります。

ボリューム: 永続性の問題を解決

コンテナは設計上一時的です。コンテナを削除すると、その中のすべてが消えます。これはステートレスアプリケーションには最適ですが、データベースや保持する必要のあるデータには問題があります。

ボリュームは、コンテナのファイルシステムの外にデータを保存することでこれを解決します。コンテナが削除され再作成されても、データは永続化されます。マウントには3つのタイプがあります:

Dockerfileの構造と解剖

Dockerfileは、Dockerイメージをビルドするための命令を含むテキストドキュメントです。各命令はイメージにレイヤーを作成し、Dockerは効率化のためにこれらのレイヤーをキャッシュします。

最も一般的なDockerfile命令の内訳は次のとおりです:

命令 目的 ベストプラクティス
FROM ビルド元のベースイメージ FROM node:22-alpine 特定のバージョンを使用、alpineを優先
WORKDIR 作業ディレクトリを設定 WORKDIR /app 絶対パスを使用
COPY ホストからイメージにファイルをコピー COPY package.json . キャッシュのため依存関係を最初にコピー
RUN ビルド中にコマンドを実行 RUN npm install レイヤーを減らすため&&でコマンドを連結
EXPOSE アプリがリッスンするポートを文書化 EXPOSE 3000 文書化のみ、ポートは公開しない
ENV 環境変数を設定 ENV NODE_ENV=production 設定に使用
USER 後続のコマンドのユーザーを設定 USER node 本番環境ではrootとして実行しない
CMD コンテナ起動時のデフォルトコマンド CMD ["node", "server.js"] JSON配列形式を使用

レイヤーキャッシングの理解

Dockerは上から下へ、レイヤーごとにイメージをビルドします。各命令が新しいレイヤーを作成します。レイヤーが変更されていない場合、Dockerは再ビルドする代わりにキャッシュされたバージョンを再利用します。

これが、Dockerfileを構造化して、変更頻度の低い命令を上部に、頻繁に変更される命令を下部に配置すべき理由です。例えば、依存関係はソースコードよりも変更頻度が低いため、ソースファイルをコピーする前に依存関係をインストールします。

# 悪い例: すべてを最初にコピーしてからインストール
COPY . .
RUN npm install

# 良い例: 依存関係を最初にインストール、キャッシュを活用
COPY package*.json ./
RUN npm install
COPY . .

Dockerfileのベストプラクティス

効率的なDockerfileを書くことは芸術です。複数のベストプラクティスを示す本番環境対応の例を以下に示します:

# マルチステージビルド: ビルダーステージ
FROM node:22-alpine AS builder
WORKDIR /app

# より良いキャッシュのため依存関係ファイルを最初にコピー
COPY package*.json ./
RUN npm ci --production

# ソースコードをコピーしてビルド
COPY . .
RUN npm run build

# マルチステージビルド: 本番ステージ
FROM node:22-alpine
WORKDIR /app

# ビルダーから必要なもののみをコピー
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./

# セキュリティ: 非rootユーザーとして実行
USER node

# ポートを文書化(実際には公開しない)
EXPOSE 3000

# コンテナオーケストレーション用のヘルスチェック
HEALTHCHECK --interval=30s --timeout=3s \
  CMD node healthcheck.js || exit 1

# アプリケーションを起動
CMD ["node", "dist/server.js"]

マルチステージビルド: 劇的なサイズ削減

マルチステージビルドを使用すると、1つのDockerfileで複数のFROMステートメントを使用できます。各FROMは新しいステージを開始し、前のステージから成果物をコピーできます。

魔法は、最終ステージのみがイメージになることで起こります。ビルドツール、コンパイラ、中間ファイルは以前のステージに残り、本番環境には含まれません。これによりイメージサイズを10倍以上削減できます。

クイックヒント: AS builderでステージに名前を付けると、後でCOPY --from=builderで参照できます。これによりDockerfileがより読みやすく保守しやすくなります。

Alpineイメージ: 小さいが強力

Alpine Linuxは、わずか5MBのサイズの最小限のLinuxディストリビューションです。これをUbuntuベースのイメージの900MB以上と比較してください。ほとんどのアプリケーションでは、Alpineは必要なすべてを提供します。

トレードオフは、Alpineがglibcの代わりにmusl libcを使用することで、プリコンパイルされたバイナリとの互換性の問題が時々発生する可能性があることです。95%のユースケースでは、Alpineは完璧に機能し、イメージサイズ、ダウンロード時間、攻撃面を劇的に削減します。

.dockerignoreファイル

.gitignoreと同様に、.dockerignoreファイルは、イメージをビルドする際にDockerが除外するファイルを指定します。これによりビルドが高速化され、イメージサイズが削減されます。

# .dockerignoreの例
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.env.local
dist
coverage
.vscode
.idea
*.log
.DS_Store

セキュリティのベストプラクティス

セキュリティは最初からDockerfileに組み込む必要があります:

最適化されたDockerfileの生成にお困りですか? Dockerfile生成ツールをお試しください。

必須のDockerコマンド

これらのコマンドをマスターすれば、日常的なDockerの使用の90%をカバーできます。各コマンドには実用的な例と一般的なフラグが含まれています。

ビルドと実行

# 現在のディレクトリのDockerfileからイメージをビルド
docker build -t myapp:1.0 .

# ビルド引数を使用してビルド
docker build --build-arg NODE_ENV=production -t myapp:1.0 .

# キャッシュなしでビルド(強制再ビルド)
docker build --no-cache -t myapp:1.0 .

# デタッチモードでコンテナを実行
docker run -d -p 3000:3000 --name myapp myapp:1.0

# 環境変数を使用して実行
docker run -d -p 3000:3000 -e NODE_ENV=production --name myapp myapp:1.0

# ボリュームマウントで実行
docker run -d -p 3000:3000 -v $(pwd)/data:/app/data --name myapp myapp:1.0

# シェルアクセスで対話的に実行
docker run -it --rm myapp:1.0 sh

コンテナの管理

# 実行中のコンテナをリスト
docker ps

# すべてのコンテナをリスト(停止中を含む)
docker ps -a

# コンテナログを表示
docker logs myapp

# リアルタイムでログをフォロー
docker logs -f myapp

# ログの最後の100行を表示
docker logs --tail 100 myapp

# 実行中のコンテナでコマンドを実行
docker exec -it myapp sh

# ワンオフコマンドを実行
docker exec myapp npm test

# コンテナを正常に停止
docker stop myapp

# コンテナを即座に強制終了
docker kill myapp

# 停止したコンテナを削除
docker rm myapp

# 1つのコマンドで停止して削除
docker stop myapp && docker rm myapp

# すべての停止したコンテナを削除
docker container prune

イメージの操作

# すべてのイメージをリスト
docker images

# カスタム形式でイメージをリスト
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

# イメージを削除
docker rmi myapp:1.0

# すべての未使用イメージを削除
docker image prune -a

# イメージにタグを付ける
docker tag myapp:1.0 myapp:latest

# レジストリにプッシュ
docker push myregistry.com/myapp:1.0

# レジストリからプル
docker pull myregistry.com/myapp:1.0

# イメージをtarファイルに保存
docker save myapp:1.0 > myapp.tar

# tarファイルからイメージをロード
docker load < myapp.tar

システム管理

# ディスク使用量を表示
docker system df

# すべての未使用データを削除(コンテナ、ネットワーク、イメージ、キャッシュ)
docker system prune -a

# リアルタイムのコンテナ統計を表示
docker stats

# コンテナの詳細を検査(JSON出力)
docker inspect myapp

# コンテナプロセスを表示
docker top myapp

プロのヒント: テスト用にコンテナを実行する際は--rmフラグを追加してください。これにより、コンテナが停止したときに自動的に削除され、システムがクリーンに保たれます: docker run --rm -it myapp:1.0 sh

マルチコンテナアプリのためのDocker Compose

Docker Composeは、マルチコンテナアプリケーションを定義して実行するためのツールです。複数のdocker runコマンドを実行する代わりに、すべてを1つのYAMLファイルで定義します。

これは、通常複数のサービス(Webサーバー、データベース、キャッシュ、メッセージキューなど)で構成される最新のアプリケーションには不可欠です。

完全なDocker Composeの例

# docker-compose.yml
version: '3.8'

services:
  # W