開発者のためのDocker: イメージ、コンテナ、Compose
あなたのマシンでは動作するアプリを構築したのに、デプロイすると問題が発生する。依存関係が欠けていたり、バージョンが異なっていたり、設定がごちゃごちゃになっていたりする。Dockerは、アプリケーションとその環境を単一のポータブルな単位にパッケージ化することでこれを解決します。この記事では、Dockerイメージ、コンテナ、Docker Composeを実践的な例とともに解説し、開発ワークフローを効率化する方法を紹介します。
Dockerイメージとは?
Dockerイメージは、アプリケーションコード、ランタイム、ライブラリ、環境変数、設定ファイルを含む読み取り専用のテンプレートです。アプリの環境のスナップショットだと考えてください。イメージはDockerfileからビルドされます。これは指示を記述したテキストファイルです。
以下はNode.jsアプリ用のシンプルなDockerfileです:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
各指示はレイヤーを作成します。レイヤーはキャッシュされるため、コード変更後の再ビルドでは変更されたレイヤー以降のステップだけが再実行されます。これによりビルドが高速になります。
Dockerコンテナとは?
コンテナはイメージの実行インスタンスです。独自のファイルシステム、ネットワーク、プロセス空間を持つ分離されたプロセスですが、ホストOSのカーネルを共有します。これにより、仮想マシンと比べてコンテナは軽量で起動が高速になります。
イメージからコンテナを実行するには:
docker run -d -p 3000:3000 --name myapp my-node-app
これはコンテナをデタッチモード(-d)で起動し、ホストのポート3000をコンテナのポート3000にマッピングし(-p 3000:3000)、myappという名前を付けます。
コンテナはデフォルトで一時的です。内部に書き込まれたデータはコンテナ停止時に失われます。データを永続化するにはボリュームを使用します:
docker run -v /host/data:/app/data my-node-app
なぜDocker Composeを使うのか?
実際のアプリケーションでは、Webサーバー、データベース、キャッシュなど、複数のサービスが必要になることがよくあります。それぞれをdocker runで実行し手動でリンクするのは面倒です。Docker Composeを使えば、単一のYAMLファイルでマルチコンテナアプリを定義して実行できます。
以下はNode.jsアプリとPostgreSQL、Redisのためのdocker-compose.ymlです:
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
db:
image: postgres:15
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
cache:
image: redis:7
volumes:
pgdata:
すべてを実行するには:
docker compose up -d
Composeはサービスが名前(例:db、cache)で互いに到達できるネットワークを作成します。また、ボリュームの作成と依存関係の順序も処理します。
開発者向けの主要なDockerコマンド
docker build -t myapp .– カレントディレクトリのDockerfileからイメージをビルドします。docker images– ローカルイメージを一覧表示します。docker ps– 実行中のコンテナを一覧表示します(すべて表示するには-aを追加)。docker exec -it myapp sh– 実行中のコンテナ内でシェルを開きます。docker logs myapp– コンテナのログを表示します。docker compose down– Composeで定義されたコンテナ、ネットワーク、ボリュームを停止して削除します。
開発におけるDockerのベストプラクティス
- .dockerignoreを使う:node_modules、.git、その他の不要なファイルをビルドコンテキストから除外します。
- マルチステージビルドを活用する:本番イメージを小さく保ちます。例えば、あるステージでアプリをビルドし、成果物だけを最小限のランタイムイメージにコピーします。
- バージョンを固定する:Dockerfileで(例:
node:latestではなくnode:18-alpine)再現可能なビルドのためにバージョンを固定します。 - 開発中はコードにボリュームを使う:ホットリロードのためにComposeで
volumes: - .:/appを設定します。 - 本番ではrootで実行しない:Dockerfileで非rootユーザーを作成します。
よくある落とし穴と回避方法
落とし穴1:肥大化したイメージ。 ubuntuのような完全なOSベースイメージを使うと、1GB以上のイメージになることがあります。slimやalpineのバリアントを使いましょう。
落とし穴2:遅いビルド。 Dockerfileの指示を変更頻度の低いものから高いものへと並べます。ソースコードをコピーする前にパッケージファイルをコピーして依存関係をインストールします。
落とし穴3:データ損失。 データベースには常にボリュームを使います。ボリュームがないと、コンテナ削除時にデータが消えます。
落とし穴4:ネットワークの混乱。 Composeでは、サービスはデフォルトネットワーク上でサービス名をホスト名として通信します。別のサービスに到達するためにlocalhostを使わないでください。
FAQ
イメージとコンテナの違いは何ですか?
イメージは読み取り専用のテンプレートで、コンテナはそのイメージの実行インスタンスです。同じイメージから複数のコンテナを実行できます。
本番でDocker Composeを使えますか?
Composeは主に開発とテスト用です。本番ではKubernetesやDocker Swarmのようなオーケストレーターを検討してください。ただし、シンプルなデプロイであればComposeでも機能します。
新しいコードでコンテナを更新するには?
docker buildでイメージを再ビルドし、コンテナを再作成します:docker compose up -d --build。
開発ワークフローを効率化する準備はできましたか?Nginx Log Analyzerを試して、コンテナ化されたWebサーバーのログを素早くデバッグしましょう。