はい、承知いたしました。Docker Composeを使った環境構築と使い方について、詳細な説明を含む約5000語の記事を作成します。
Docker Compose を使った環境構築と使い方
はじめに
現代のソフトウェア開発において、開発環境の構築はしばしば大きな課題となります。OSの違い、ライブラリのバージョン依存関係、ミドルウェアのインストールと設定など、多くの要素が絡み合い、開発者ごとに環境が異なったり、新しいメンバーがプロジェクトに参加する際の初期設定に時間がかかったりすることが少なくありませんでした。これは、開発効率の低下や「私の環境では動くのに…」といった問題を引き起こす原因となります。
このような課題を解決するために登場したのが「コンテナ技術」であり、その代表格が「Docker」です。Dockerは、アプリケーションとその実行に必要な全ての要素(コード、ランタイム、システムツール、ライブラリなど)を一つの「コンテナ」としてパッケージ化し、どの環境でも一貫して動作することを可能にします。これにより、開発環境と本番環境の差異をなくし、アプリケーションのデプロイやスケーリングを容易にします。
しかし、実際のアプリケーションは、Webサーバー、アプリケーションサーバー、データベース、キャッシュサーバーなど、複数のサービスで構成されていることがほとんどです。これらのサービスそれぞれを個別のDockerコンテナとして管理する場合、手動で複数のコンテナを起動・停止・連携させるのは煩雑です。
ここで登場するのが「Docker Compose」です。Docker Composeは、複数のDockerコンテナから成るアプリケーションを定義し、まとめて管理するためのツールです。YAMLファイル一つにアプリケーションの構成(どのイメージを使うか、ポート設定、ボリューム設定、サービス間の依存関係など)を記述するだけで、複雑な複数コンテナアプリケーションを簡単に起動、停止、再起動できます。
この記事では、Docker Composeを使った環境構築の基本的な考え方から、docker-compose.yml
ファイルの書き方、主要なコマンドの使い方、そして実際のWebアプリケーションとデータベースの構成を例にした実践的なサンプルプロジェクトを通じて、Docker Composeを使いこなす方法を詳細に解説します。開発環境の構築に課題を感じている方、複数コンテナアプリケーションの管理を効率化したい方は、ぜひ最後までお読みください。
DockerとDocker Composeの基本
Docker Composeを理解するためには、まずDockerの基本的な概念を知っておく必要があります。
Dockerとは?
Dockerは、コンテナという軽量な仮想化技術を使用してアプリケーションを開発、配布、実行するためのプラットフォームです。
- コンテナ (Container): アプリケーションとその依存関係を全て含んだ、軽量で実行可能なパッケージです。ホストOSのカーネルを共有するため、仮想マシンに比べて起動が速く、ディスク容量も少なくて済みます。コンテナは分離されており、互いに影響を与えません。
- イメージ (Image): コンテナを作成するための読み取り専用のテンプレートです。Dockerfileという設定ファイルに記述された手順に基づいて作成されます。イメージはDocker Hubなどのコンテナレジストリから取得したり、自分で作成したりできます。
- Dockerfile: Dockerイメージを自動的に構築するための一連の手順を記述したテキストファイルです。例えば、「Ubuntuイメージをベースにする」「特定のパッケージをインストールする」「アプリケーションコードをコピーする」「起動コマンドを指定する」といった手順を記述します。
Dockerを使うことで、特定のアプリケーションを実行するために必要な環境をイメージとして固定し、そのイメージからコンテナを起動するだけで、どんな環境でも同じようにアプリケーションを動作させることができます。
なぜDocker Composeが必要か?
単一のコンテナで完結するシンプルなアプリケーションであれば、Dockerコマンド(docker run
など)だけで十分かもしれません。しかし、多くの実際のアプリケーションは複数のサービスで構成されます。例えば、以下のような構成は一般的です。
- Webアプリケーション: ユーザーからのリクエストを受け付け、ビジネスロジックを実行する(例: Node.js, Python/Django, Ruby/Rails, Java/Springなど)
- データベース: データを永続的に保存する(例: PostgreSQL, MySQL, MongoDBなど)
- キャッシュ: データの読み取りパフォーマンスを向上させる(例: Redis, Memcachedなど)
- メッセージキュー: サービス間の非同期通信を行う(例: RabbitMQ, Kafkaなど)
これらの各サービスを個別のDockerコンテナとして起動し、互いに連携させる必要があります。
手動でこれらのコンテナを管理しようとすると、以下のような手間が発生します。
- 各コンテナごとに
docker run
コマンドを実行する必要がある。その際、ポートマッピング、ボリュームマッピング、環境変数、ネットワーク設定などを正確に指定する必要がある。 - サービス間の依存関係(例: Webアプリケーションはデータベースが起動してから起動する必要がある)を手動で管理する必要がある。
- アプリケーション全体を停止・再起動する際に、各コンテナを個別に停止・再起動する必要がある。
このような複数のコンテナから成るアプリケーションの定義、起動、停止、管理を効率化するために開発されたのがDocker Composeです。
Docker Composeのメリット
Docker Composeを利用することで、主に以下のようなメリットが得られます。
- アプリケーション構成の一元化: アプリケーションを構成する全サービスの定義(使用するイメージ、ポート、ボリューム、環境変数、依存関係など)を、
docker-compose.yml
という単一のYAMLファイルに集約できます。 - 簡単な起動・停止: 定義ファイルに基づいて、コマンド一つ(
docker-compose up
)でアプリケーション全体(全てのサービスコンテナ)をまとめて起動できます。停止もコマンド一つ(docker-compose down
)で可能です。 - 再現性の高い環境:
docker-compose.yml
ファイルを共有することで、開発チームの誰もが同じ構成の環境を簡単に構築できます。これにより、「開発者Aの環境では動くのに、開発者Bの環境では動かない」といった問題を減らせます。 - サービスの連携: 同じDocker Composeプロジェクト内で定義されたサービスは、デフォルトで同じネットワークに接続され、サービス名で互いに通信できます。また、
depends_on
オプションでサービス間の起動順序を制御できます。 - 開発とテストの効率化: ローカル開発環境として、本番に近い多層構造のアプリケーション環境を容易に構築・操作できるため、開発や結合テストが効率的に行えます。
Docker Composeを始める準備
Docker Composeを使用するには、まずDockerとDocker Composeがインストールされている必要があります。
1. Docker DesktopまたはDocker Engineのインストール
お使いのOSに応じて、Dockerをインストールします。
- Windows / macOS: Docker Desktopをインストールするのが最も簡単です。Docker Desktopには、Docker Engine、Docker CLI、Docker Compose v2などが含まれています。公式ウェブサイトからインストーラーをダウンロードして実行してください。
- Linux: 各ディストリビューションのパッケージマネージャーを使ってDocker Engineをインストールします。Docker Engineのインストール後、Docker Compose v2(
docker compose
コマンドとして提供)またはv1(docker-compose
コマンドとして提供)を別途インストールする必要があります。最新版のDocker Engineをインストールすれば、通常はDocker Compose v2が一緒にインストールされます。
インストール後、ターミナルを開いて以下のコマンドを実行し、DockerとDocker Composeが正しくインストールされているか確認します。
bash
docker --version
docker compose version # または docker-compose --version (古いv1の場合)
バージョン情報が表示されれば準備完了です。
2. プロジェクト構成の例
Docker Composeは通常、アプリケーションのプロジェクトルートディレクトリに docker-compose.yml
ファイルを配置して使用します。典型的なプロジェクト構造は以下のようになります。
my-awesome-app/
├── app/ # アプリケーションコード
│ ├── src/
│ └── Dockerfile # アプリケーション用のDockerfile
├── db/ # データベース関連ファイル (初期化スクリプトなど)
├── docker-compose.yml # Docker Compose 設定ファイル
├── .env # 環境変数ファイル (オプション)
└── ... # その他のプロジェクトファイル
この記事のサンプルプロジェクトでは、このような構成を想定して説明を進めます。
docker-compose.ymlの書き方
docker-compose.yml
ファイルは、Docker Composeアプリケーションの全ての構成を定義するYAMLファイルです。基本的な構造と主要な設定項目について詳細に解説します。
YAML (YAML Ain’t Markup Language) は、人間が読み書きしやすいデータ形式です。インデントが構造を表すため、正確なスペース/タブの利用が重要です。
docker-compose.yml
の基本的な構造は以下のようになります。
“`yaml
version: ‘3.8’ # または他のバージョン。Composeファイルの仕様バージョンを指定
services:
# サービス定義1
service1_name:
# サービスの設定項目…
# サービス定義2
service2_name:
# サービスの設定項目…
名前付きボリュームの定義 (必要な場合)
volumes:
volume1_name:
volume2_name:
ネットワークの定義 (必要な場合)
networks:
network1_name:
network2_name:
“`
各セクションについて詳しく見ていきましょう。
version
Docker Composeファイルの仕様バージョンを指定します。各バージョンで利用できる機能や構文が異なります。特に指定がない限り、比較的新しい '3.8'
などのバージョンを使うのが一般的です。
yaml
version: '3.8'
services
アプリケーションを構成する各サービスを定義するセクションです。各サービスはキー(サービス名)と値(そのサービスの設定オブジェクト)のペアとして記述します。サービス名は、同じDocker Composeプロジェクト内でコンテナ同士が通信する際のホスト名としても利用できます。
yaml
services:
web: # Webサービス
# 設定...
db: # データベースサービス
# 設定...
cache: # キャッシュサービス
# 設定...
各サービスの設定オブジェクト内でよく使用される主要な設定項目を以下に示します。
-
image
: サービスに使用するDockerイメージを指定します。Docker Hubなどのレジストリに存在する既存のイメージを使用する場合に指定します。image: postgres:14
のようにイメージ名とタグを指定するのが一般的です。タグを省略すると最新版 (latest
) が使用されますが、再現性のために特定のタグを指定することを推奨します。yaml
services:
db:
image: postgres:14 -
build
: サービスに使用するDockerイメージを、Dockerfileから自分で構築する場合に指定します。image
とbuild
は同時に指定できません。build
はパス文字列、または設定オブジェクトとして指定できます。-
パス文字列として指定する場合: 指定されたパスにあるディレクトリがビルドコンテキストとなり、その中の
Dockerfile
という名前のファイルが使用されます。yaml
services:
web:
build: ./app # プロジェクトルートからの相対パス -
設定オブジェクトとして指定する場合: より詳細なビルド設定が可能です。
context
: ビルドコンテキストのパスを指定します。通常はDockerfileが存在するディレクトリを指定します。dockerfile
: 使用するDockerfileのファイル名を指定します。デフォルトはDockerfile
ですが、他の名前のファイルを使用する場合に指定します。args
: DockerfileのARG
命令に渡すビルド引数を指定します。
yaml
services:
web:
build:
context: ./app
dockerfile: Dockerfile.dev # Dockerfileの名前を変更した場合
args:
NODE_VERSION: 18
-
-
ports
: ホストマシンとコンテナ間でポートマッピングを設定します。ホストのポートにアクセスすると、コンテナの指定ポートに転送されます。-
ショートシンタックス:
ホストポート:コンテナポート
またはIPアドレス:ホストポート:コンテナポート
の形式で指定します。一般的にはホストポート:コンテナポート
を使用します。yaml
services:
web:
ports:
- "80:80" # ホストの80番ポートをコンテナの80番ポートにマッピング
- "443:443" # ホストの443番ポートをコンテナの443番ポートにマッピング -
ロングシンタックス: より詳細な設定が可能です。
target
(コンテナポート),published
(ホストポート),protocol
(tcp/udp),mode
(host/ingress) などを指定できます。yaml
services:
web:
ports:
- target: 80
published: 8080
protocol: tcp
mode: host
ports
はリストなので複数指定できます。ポート番号は文字列としてダブルクォートで囲むのが一般的ですが、数値でも問題ない場合があります(ただし:
が含まれる場合は文字列必須)。
-
-
volumes
: コンテナとホストマシンの間でファイルを共有したり、コンテナのデータを永続化したりするためにボリュームを設定します。-
Bind Mount: ホストマシンの特定のディレクトリをコンテナ内のディレクトリにマウントします。開発中にコードをリアルタイムでコンテナに反映させたい場合などに便利です。
ホストパス:コンテナパス
の形式で指定します。ホストパスはプロジェクトルートからの相対パスが一般的です。yaml
services:
web:
volumes:
- ./app:/usr/src/app # ホストの ./app ディレクトリをコンテナの /usr/src/app にマウント -
Named Volume: Dockerに管理させる名前付きボリュームを使用します。データの永続化に推奨されます。
ボリューム名:コンテナパス
の形式で指定し、トップレベルのvolumes
セクションでボリューム名を定義します。“`yaml
services:
db:
volumes:
– db_data:/var/lib/postgresql/data # ボリューム名 ‘db_data’ をコンテナの指定パスにマウントvolumes:
db_data: # トップレベルでボリューム名を定義
``
volumes` もリストなので複数指定できます。
Bind MountとNamed Volumeは混在可能です。また、
-
-
environment
: コンテナ内で使用する環境変数を設定します。アプリケーションの設定(データベース接続情報、APIキーなど)を渡すのに使用します。-
キー-値ペアのリストまたはマップとして指定します。
yaml
services:
web:
environment:
- NODE_ENV=development
- DATABASE_URL=postgres://user:password@db:5432/mydatabase # サービス名 'db' をホスト名として利用
または
yaml
services:
web:
environment:
NODE_ENV: development
DATABASE_URL: postgres://user:password@db:5432/mydatabase -
ホストの環境変数を引き継ぐこともできます。値を指定しない場合、Composeを実行しているホストの環境変数がそのままコンテナに渡されます。
yaml
services:
web:
environment:
- DEBUG=1
- MY_API_KEY # ホストの MY_API_KEY 環境変数を引き継ぐ
-
-
env_file
: 環境変数をファイルから読み込みます。機密情報や環境固有の設定をdocker-compose.yml
ファイルから分離するのに便利です。各行が変数名=値
の形式のファイル(例:.env
)を指定します。複数ファイルを指定できます。yaml
services:
web:
env_file:
- .env # プロジェクトルートの .env ファイルを読み込む
- ./app/.env_vars # アプリケーションディレクトリ内のファイルを読み込む
.env
ファイルは通常、Git管理から除外(.gitignore
に追加)し、チームメンバーやデプロイ環境間で共有する方法を検討します。Compose実行時にプロジェクトディレクトリに.env
ファイルが存在する場合、自動的に読み込まれます(明示的にenv_file: .env
と指定する必要はありませんが、指定しても問題ありません)。 -
depends_on
: サービス間の依存関係を定義します。指定されたサービスが起動(より正確には、デフォルトではコンテナが作成され、コマンドが実行可能状態になる)してから、現在のサービスが起動するようになります。yaml
services:
web:
build: ./app
ports:
- "3000:3000"
depends_on:
- db # dbサービスが起動してからwebサービスを起動する
- cache # cacheサービスも起動してからwebサービスを起動する
db:
image: postgres:14
cache:
image: redis:latest
これはあくまで「起動順序」を制御するものです。依存先のサービスが「完全に準備完了(例えば、データベースが接続を受け付ける状態になった)」を保証するものではありません。完全に準備完了を待つには、アプリケーションコード側でリトライロジックを実装するか、depends_on
のロングシンタックスとcondition
オプション(service_healthy
,service_started
,service_completed_successfully
)を使用する必要があります。特にservice_healthy
はヘルスチェックと組み合わせて使用します。yaml
services:
web:
build: ./app
depends_on:
db:
condition: service_healthy # dbサービスがHealthyになるまで待つ
db:
image: postgres:14
healthcheck: # ヘルスチェックを定義
test: ["CMD-EXEC", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5 -
networks
: サービスが接続するネットワークを指定します。指定しない場合、Docker Composeはデフォルトでプロジェクト名のネットワークを作成し、全てのサービスをそこに接続します。明示的にネットワークを定義し、複数のサービス間で共有したり、既存のネットワークに参加させたりする場合に使用します。“`yaml
services:
web:
build: ./app
networks:
– app_net # app_net ネットワークに接続db:
image: postgres:14
networks:
– app_net # app_net ネットワークに接続networks:
app_net: # トップレベルでネットワークを定義
driver: bridge
“`
同じネットワーク上のサービスは、互いにサービス名をホスト名として通信できます。 -
container_name
: 作成されるコンテナに任意の名前を指定します。指定しない場合、プロジェクト名_サービス名_番号
のような形式で自動的に名前が付けられます。一意でなければなりません。yaml
services:
db:
image: postgres:14
container_name: my_postgres_db # コンテナ名を固定する -
command
: コンテナ起動時に実行されるコマンドを上書きします。DockerfileのCMD
命令やイメージのデフォルトコマンドを無視して、ここで指定したコマンドが実行されます。yaml
services:
worker:
image: my_worker_image
command: python ./worker.py --queue high_priority # デフォルトコマンドを上書き -
entrypoint
: コンテナの実行エントリポイントを上書きします。DockerfileのENTRYPOINT
命令やイメージのデフォルトエントリポイントを無視して、ここで指定したコマンドがエントリポイントとなります。command
と組み合わせて使用されることが多いです。yaml
services:
web:
image: my_web_image
entrypoint: ["/usr/local/bin/wait-for-it.sh", "db:5432", "--"] # エントリポイントを指定
command: ["node", "index.js"] # エントリポイントに引数として渡されるコマンド -
restart
: コンテナが停止した場合の再起動ポリシーを指定します。no
: 再起動しない (デフォルト)。on-failure
: エラー終了した場合のみ再起動する。always
: 常に再起動する(手動で停止するまで)。unless-stopped
: 明示的に停止された場合以外は常に再起動する。
開発環境では
no
やon-failure
が多いですが、本番に近い構成ではalways
やunless-stopped
が使われることもあります。yaml
services:
web:
build: ./app
restart: unless-stopped # 明示的に停止しない限り常に再起動 -
その他のよく使うオプション:
working_dir
: コンテナ内でコマンドを実行する際の作業ディレクトリを指定します。user
: コンテナ内でプロセスを実行するユーザーを指定します。ulimits
: コンテナのリソース制限(ファイルディスクリプタ数など)を設定します。labels
: コンテナにメタデータを付与するためのラベルを設定します。healthcheck
: コンテナのヘルスチェック方法を定義します(depends_on
のservice_healthy
と組み合わせて使用)。
これらの設定項目を組み合わせることで、アプリケーションの構成を柔軟に定義できます。
具体的なサンプルプロジェクト:Webアプリケーション + データベース
ここでは、Node.jsの簡単なWebアプリケーションとPostgreSQLデータベースからなるアプリケーションを例に、docker-compose.yml
と関連ファイルを作成し、Docker Composeで起動する手順を解説します。
プロジェクトの要件:
* Node.jsで書かれたシンプルなWebサーバー
* PostgreSQLデータベースに接続し、データの読み書きを行う
* 開発中は、ホストマシンでコードを編集するとコンテナ内のコードも更新されるようにする(ホットリロード)
* データベースのデータはコンテナを再起動しても永続化されるようにする
* データベース接続情報は環境変数で設定する
プロジェクトディレクトリ構成は以下のようになります。
my-web-app/
├── app/ # Node.js アプリケーションディレクトリ
│ ├── index.js # Webサーバーコード
│ ├── package.json # Node.js パッケージファイル
│ └── Dockerfile # Node.js アプリケーション用 Dockerfile
├── .env # 環境変数ファイル (DB接続情報など)
└── docker-compose.yml # Docker Compose 設定ファイル
1. アプリケーションコード (app/
)
まず、簡単なNode.jsアプリケーションを作成します。
app/package.json
:
json
{
"name": "my-web-app",
"version": "1.0.0",
"description": "Simple web app connecting to PostgreSQL",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.11.3"
}
}
app/index.js
:
“`javascript
const express = require(‘express’);
const { Pool } = require(‘pg’);
const app = express();
const port = 3000;
// 環境変数からDB接続情報を取得
const dbConfig = {
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
host: process.env.DB_HOST, // Docker Composeサービス名がホスト名になる
database: process.env.DB_NAME,
port: process.env.DB_PORT || 5432,
};
// データベース接続プールを作成
let pool;
async function connectDb() {
try {
pool = new Pool(dbConfig);
await pool.query(‘SELECT NOW()’); // 接続テスト
console.log(‘Database connection successful!’);
} catch (err) {
console.error(‘Database connection failed:’, err);
// 接続失敗してもサーバーは起動しておく(depends_onは接続準備完了を保証しないため)
// 実際にはリトライ処理などを入れるべき
}
}
connectDb(); // アプリ起動時にDB接続を試みる
app.get(‘/’, async (req, res) => {
if (!pool) {
return res.status(500).send(‘Database not connected’);
}
try {
const result = await pool.query(‘SELECT NOW() as now’);
res.send(Hello from Web App! Current time from DB: ${result.rows[0].now}
);
} catch (err) {
console.error(‘Error executing query’, err);
res.status(500).send(‘Error retrieving data from database’);
}
});
app.listen(port, () => {
console.log(Web app listening on port ${port}
);
});
“`
app/Dockerfile
:
“`dockerfile
ベースイメージとして公式のNode.jsイメージを使用
FROM node:18-alpine
コンテナ内の作業ディレクトリを設定
WORKDIR /usr/src/app
アプリケーションの依存関係をインストール
package.jsonとpackage-lock.json (または yarn.lock) をコピーしてnpm installを実行することで、
コードの変更があっても依存関係の再インストールが不要になるようにレイヤーキャッシュを活用
COPY package*.json ./
RUN npm install
アプリケーションコードをコンテナにコピー
COPY . .
アプリケーションがリッスンするポートを公開
EXPOSE 3000
アプリケーション起動コマンド
CMD [“npm”, “start”]
“`
2. 環境変数ファイル (.env
)
データベース接続情報などの環境変数を記述します。
.env
:
dotenv
DB_USER=myuser
DB_PASSWORD=mypassword
DB_NAME=mydatabase
DB_HOST=db # Docker Composeのサービス名(dbサービス)
DB_PORT=5432
注意: .env
ファイルは通常、Git管理から除外します(.gitignore
に /.env
を追加)。実際の値は、チームメンバー間で共有したり、デプロイ時に安全な方法で設定したりします。
3. Docker Compose設定ファイル (docker-compose.yml
)
Webサービスとデータベースサービス、およびデータの永続化に必要なボリュームを定義します。
docker-compose.yml
:
“`yaml
version: ‘3.8’
services:
web:
build: ./app # ./app ディレクトリにあるDockerfileからイメージをビルド
ports:
– “3000:3000” # ホストの3000番ポートをコンテナの3000番ポートにマッピング
volumes:
– ./app:/usr/src/app # ホストの ./app ディレクトリをコンテナの /usr/src/app にバインドマウント
– /usr/src/app/node_modules # node_modules ディレクトリはホストと共有せず、コンテナ内でインストールされたものを使用(匿名ボリューム)
environment:
# .env ファイルで設定した環境変数を読み込むため、ここでは特に指定不要だが、
# デバッグ用やローカル環境専用の変数があればここで設定可能
# 例: NODE_ENV: development
# ただし、.envファイルで設定した値が優先される
DB_HOST: db # DBサービスへの接続ホスト名。サービス名で通信可能。
depends_on:
db:
condition: service_healthy # dbサービスがhealthcheckに合格するまで待つ
db:
image: postgres:14 # PostgreSQLの公式イメージを使用
ports:
– “5432:5432” # オプション: ホストからもDBに直接アクセスしたい場合にポートマッピング
volumes:
– db_data:/var/lib/postgresql/data # 名前付きボリュームでDBデータを永続化
environment:
# PostgreSQLの公式イメージで初期設定に使用される環境変数
POSTGRES_USER: ${DB_USER} # .env ファイルから読み込む
POSTGRES_PASSWORD: ${DB_PASSWORD} # .env ファイルから読み込む
POSTGRES_DB: ${DB_NAME} # .env ファイルから読み込む
healthcheck: # DBのヘルスチェック定義
test: [“CMD-EXEC”, “pg_isready -U ${POSTGRES_USER}”]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s # DB起動後、最初のヘルスチェックを開始するまでの猶予期間
volumes:
db_data: # データベースデータ用の名前付きボリュームを定義
“`
解説:
-
webサービス:
build: ./app
:./app
ディレクトリにあるDockerfile
を使ってイメージをビルドします。ports: - "3000:3000"
: ホストの3000番ポートへのアクセスをコンテナの3000番ポートに転送します。Webブラウザからhttp://localhost:3000
でアクセスできるようになります。volumes: - ./app:/usr/src/app
: ホストの./app
ディレクトリ(アプリケーションコードがある場所)をコンテナの/usr/src/app
にマウントします。これにより、ホストでコードを編集すると、コンテナ内のコードもすぐに反映されます。Node.jsアプリケーションの場合、nodemonなどのツールを使えばコード変更時にサーバーを自動再起動させることが可能です。volumes: - /usr/src/app/node_modules
: この行は少し特殊です。上のBind Mountでホストの./app
全体がコンテナの/usr/src/app
にマウントされますが、通常、node_modules
はOSやアーキテクチャに依存するバイナリを含むため、ホスト側でnpm install
したものをコンテナに持ち込みたくありません。コンテナ内でnpm install
されたnode_modules
を使うのが正しい方法です。この行は、コンテナ内の/usr/src/app/node_modules
パスに匿名ボリューム(ホストの特定パスに紐づかない)をマウントすることで、先に定義した./app:/usr/src/app
のBind Mountでホストのnode_modules
がコンテナに上書きされてしまうのを防ぎます。結果として、コンテナ内でRUN npm install
されたnode_modules
ディレクトリの内容が使用されます。environment: DB_HOST: db
: Webアプリケーションがデータベースに接続する際のホスト名をdb
と指定しています。Docker Composeのデフォルトネットワークでは、サービス名がそのまま他のサービスからのアクセス可能なホスト名として機能します。depends_on: db: condition: service_healthy
: WebサービスはDBサービスが利用可能になってから起動するように依存関係を設定しています。service_healthy
条件は、DBサービスのヘルスチェックが成功するまでWebサービスの起動を待機させます。
-
dbサービス:
image: postgres:14
: Docker Hubから公式のpostgres:14
イメージを取得して使用します。ports: - "5432:5432"
: ホストの5432番ポート(PostgreSQLデフォルトポート)をコンテナの5432番ポートにマッピングします。これは、ホストからDBクライアントツール(pgAdminなど)を使って直接データベースに接続したい場合に便利です。必須ではありません。volumes: - db_data:/var/lib/postgresql/data
: PostgreSQLのデータディレクトリをdb_data
という名前付きボリュームにマッピングします。コンテナが停止・削除されても、db_data
ボリュームが残っていればデータは失われません。environment: POSTGRES_USER: ${DB_USER}...
: PostgreSQLイメージの初期設定に必要な環境変数を設定します。$
記法を使って、プロジェクトルートの.env
ファイルから値を読み込みます。healthcheck
: DBコンテナが実際に接続を受け付け可能になったかを確認するためのヘルスチェックを定義します。pg_isready
コマンドを使ってDBの状態を確認します。
-
volumes:
db_data:
: トップレベルでdb_data
という名前付きボリュームを定義します。これにより、Dockerがこのボリュームを管理し、データ永続化に使用できるようになります。
これで、Webアプリケーションとデータベースの構成をDocker Composeで定義できました。
docker-composeコマンドの使い方
docker-compose.yml
ファイルが準備できたら、いよいよDocker Composeコマンドを使ってアプリケーションを操作します。これらのコマンドは、docker-compose.yml
ファイルが存在するディレクトリで実行します(または -f
オプションでファイルパスを指定します)。
Docker Compose v2を使用している場合、コマンドは docker compose <subcommand>
の形式になります。古いv1を使用している場合は docker-compose <subcommand>
です。ここではv2の docker compose
コマンドを基本に説明します。
1. アプリケーションの起動 (docker compose up
)
定義ファイルに基づいて、サービスを構築(必要であれば)し、作成し、起動します。
bash
docker compose up
このコマンドは以下の処理を行います:
* docker-compose.yml
ファイルを読み込みます。
* 各サービスについて、指定されたイメージが存在しない場合、または build
が指定されている場合はイメージを構築(またはプル)します。
* 各サービスについて、コンテナを作成し、起動します。depends_on
で指定された依存関係を考慮して起動順序を調整します。
* デフォルトでは、全てのコンテナのログをターミナルにまとめて表示します。
よく使うオプション:
-
-d
(or--detach
): コンテナをバックグラウンド(デタッチモード)で起動します。ターミナルはすぐに制御を返します。bash
docker compose up -d
バックグラウンドで起動した後、ログを確認したい場合はdocker compose logs
コマンドを使用します。 -
--build
: コンテナ起動前に、build
オプションが指定されているサービスのイメージを必ず再構築します。コードを変更した場合などにイメージを更新したいときに使用します。bash
docker compose up --build
-d
と組み合わせてdocker compose up -d --build
とすることも多いです。 -
--force-recreate
: 既に存在するコンテナを一度削除してから再作成します。設定(ポート、ボリュームなど)を変更した場合や、コンテナの状態をリフレッシュしたい場合に便利です。bash
docker compose up --force-recreate -
-V
(or--renew-anon-volumes
): 匿名ボリューム(名前なしボリューム、例:volumes: - /usr/src/app/node_modules
)を再作成します。通常は既存の匿名ボリュームが再利用されますが、これを指定すると新しい匿名ボリュームが作成されます。 -
[サービス名...]
: 特定のサービスのみを起動します。依存関係にあるサービスも必要に応じて起動されます。bash
docker compose up web # webサービスとそれに依存するdbサービスを起動
サンプルプロジェクトの場合、プロジェクトルートディレクトリで .env
ファイルと docker-compose.yml
がある状態で docker compose up
を実行すると、Node.jsイメージのビルド、PostgreSQLイメージのプル、DBコンテナの起動、Webコンテナの起動が行われます。
bash
cd my-web-app
docker compose up
しばらく待つと、各サービスのログが表示され、Webサーバーが起動したメッセージと、DBに接続成功したメッセージが表示されるはずです。ブラウザで http://localhost:3000
にアクセスすると、Webアプリケーションのレスポンスが表示されることを確認できます。
バックグラウンドで起動する場合は docker compose up -d
を実行します。
2. アプリケーションの停止 (docker compose down
)
docker compose up
で起動したサービスを停止し、関連するコンテナとデフォルトネットワークを削除します。
bash
docker compose down
よく使うオプション:
-
--volumes
(or-v
): コンテナだけでなく、サービスで定義された名前付きボリュームも削除します。データベースのデータを完全にリセットしたい場合などに使用します。注意: このオプションを使うとデータが消えますので、慎重に使用してください。bash
docker compose down --volumes -
--rmi <type>
: サービスが使用しているイメージも削除します。type
にはlocal
(カスタムビルドされたイメージのみ)またはall
(プルしたイメージも含む)を指定できます。bash
docker compose down --rmi local # カスタムビルドイメージを削除
docker compose down --rmi all # カスタムビルドおよびプルしたイメージを削除
3. サービスの稼働状況確認 (docker compose ps
)
現在実行中のサービスコンテナのリストと状態を表示します。
bash
docker compose ps
出力例:
NAME IMAGE COMMAND CREATED STATUS PORTS
my-web-app-db-1 postgres:14 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 0.0.0.0:5432->5432/tcp
my-web-app-web-1 my-web-app-web "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 0.0.0.0:3000->3000/tcp
コンテナ名、使用イメージ、実行コマンド、作成日時、ステータス、ポートマッピングが表示されます。Up
であれば実行中です。ヘルスチェックを定義しているサービスの場合、ステータスが Up (healthy)
や Up (unhealthy)
のように表示されることもあります。
4. サービスのログ表示 (docker compose logs
)
サービスの標準出力と標準エラー出力を表示します。コンテナが起動しない、エラーが発生するといったトラブルシューティングに必須のコマンドです。
bash
docker compose logs
全てのサービスのログをまとめて表示します。
よく使うオプション:
-
[サービス名...]
: 特定のサービスのログのみを表示します。bash
docker compose logs web # webサービスのログのみ表示
docker compose logs db # dbサービスのログのみ表示 -
-f
(or--follow
): ログをリアルタイムで追跡します(tail -f のように)。bash
docker compose logs -f # 全サービスのログをリアルタイム表示
docker compose logs -f web # webサービスのログをリアルタイム表示 -
--tail <number>
: ログの末尾から指定した行数を表示します。bash
docker compose logs --tail 100 web # webサービスの最新100行を表示 -
--since <time>
: 指定した時間以降のログを表示します。bash
docker compose logs --since 5m # 過去5分間のログを表示
5. 稼働中のコンテナ内でコマンド実行 (docker compose exec
)
稼働中の特定のサービスコンテナ内で任意のコマンドを実行します。デバッグやコンテナ内部の調査に非常に便利です。
bash
docker compose exec [サービス名] [実行したいコマンド]
例:
-
webコンテナ内でシェルを開く(bashまたはshが利用可能であれば):
“`bash
docker compose exec web bashまたは
docker compose exec web sh
“`
シェルが開いたら、コンテナ内のファイルシステムを確認したり、コマンドを実行したりできます。 -
dbコンテナ内でpsqlクライアントを開く:
bash
docker compose exec db psql -U myuser mydatabase # ユーザー名とDB名を指定 -
webコンテナ内で特定コマンドを実行:
bash
docker compose exec web npm install lodash # コンテナ内で依存関係を追加
ただし、volumes
でホストのコードをマウントしている場合は、通常ホスト側でnpm install
を実行し、その変更がマウントによってコンテナに反映されるようにします。
6. 特定サービスのイメージ構築 (docker compose build
)
build
オプションが指定されている特定のサービス、または全てのサービスのイメージを再構築します。docker compose up --build
でもビルドは行われますが、ビルドだけを単独で実行したい場合に便利です。
bash
docker compose build [サービス名...]
例:
bash
docker compose build web # webサービスイメージのみ再構築
docker compose build # 全てのbuild対象サービスイメージを再構築
7. 特定サービスの再起動 (docker compose restart
)
稼働中の特定のサービス、または全てのサービスを再起動します。
bash
docker compose restart [サービス名...]
例:
bash
docker compose restart web # webサービスのみ再起動
docker compose restart # 全てのサービスを再起動
8. 特定サービスの停止/開始 (docker compose stop
/docker compose start
)
特定のサービス、または全てのサービスを停止/開始します。stop
はコンテナを停止しますが削除はしません(ps
コマンドで見ると Exited
などの状態になります)。start
は停止中のコンテナを開始します。
bash
docker compose stop web # webサービスを停止
docker compose start web # webサービスを開始
docker compose stop # 全てのサービスを停止
docker compose start # 全てのサービスを開始
docker compose down
はコンテナを停止するだけでなく削除する点が異なります。
9. 設定ファイルの検証 (docker compose config
)
docker-compose.yml
ファイルの構文が正しいか検証し、マージされた最終的な設定内容を表示します。複数のComposeファイルを組み合わせて使用している場合などに、最終的な設定を確認するのに役立ちます。
bash
docker compose config
10. イメージのプル (docker compose pull
)
image
オプションが指定されているサービスのDockerイメージを、レジストリから事前にプルしておきます。コンテナ起動時にイメージのダウンロード時間を短縮したい場合に便利です。
bash
docker compose pull [サービス名...]
開発環境での応用
Docker Composeは特にローカル開発環境の構築に力を発揮します。いくつかの便利な応用方法を紹介します。
1. ボリュームを使ったコードのリアルタイム反映 (Bind Mount)
サンプルプロジェクトで示した volumes: - ./app:/usr/src/app
のようなBind Mountは、開発効率を大幅に向上させます。ホストマシンのエディタでコードを保存すると、その変更がすぐにコンテナ内の該当ファイルに反映されます。
Node.jsなどの一部のフレームワーク/ライブラリ(例: nodemon)は、ファイル変更を検知してアプリケーションを自動的に再起動する機能を持っています。これを組み合わせることで、非常にスムーズな開発フローを実現できます。
例えば、Node.jsアプリケーションの package.json
に nodemon をインストールして開発用スクリプトを追加します。
app/package.json
(開発用スクリプトを追加):
json
{
"name": "my-web-app",
"version": "1.0.0",
"description": "Simple web app connecting to PostgreSQL",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js" # 開発用スクリプト
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.11.3",
"nodemon": "^3.0.2" # nodemon を開発依存に追加
}
}
そして、docker-compose.yml
の web
サービスの command
または entrypoint
を変更し、開発用スクリプト (npm run dev
) を実行するようにします。
docker-compose.yml
(開発用の設定例 – 別ファイルに分ける方が一般的だが、ここでは単純化のため):
“`yaml
version: ‘3.8’
services:
web:
build: ./app
ports:
– “3000:3000”
volumes:
– ./app:/usr/src/app
– /usr/src/app/node_modules
environment:
DB_HOST: db
NODE_ENV: development # 開発環境を示す変数
depends_on:
db:
condition: service_healthy
# 開発時は nodemon で起動するコマンドに変更
command: [“npm”, “run”, “dev”] # または entrypoint と組み合わせて
# 起動が失敗しても再起動を試みる(nodemonがクラッシュした場合など)
restart: on-failure
db:
# … (省略)
healthcheck:
# … (省略)
volumes:
db_data:
``
docker compose up
この設定ですると、webサービスは nodemon で起動し、ホストで
app/index.js` を変更するたびに自動的にWebサーバーが再起動されます。
2. 複数のComposeファイルを使った環境ごとの設定分離
開発環境、テスト環境、本番環境で、サービスの構成や設定を変えたい場合があります。例えば:
* 開発環境ではBind Mountを使うが、本番環境では使わない(または別の方法でコードをコンテナに配置する)。
* 開発環境ではデバッグログを有効にするが、本番環境では無効にする。
* テスト環境ではインメモリデータベースを使う、など。
このような場合、複数の docker-compose.yml
ファイルを作成し、設定を重ね合わせる(オーバーライドする)のが効果的です。
例えば、共通の設定を docker-compose.yml
に置き、開発環境固有の設定を docker-compose.dev.yml
に置きます。
docker-compose.yml
(共通設定 – 本番に近い構成を想定):
“`yaml
version: ‘3.8’
services:
web:
build:
context: ./app
dockerfile: Dockerfile # 本番用Dockerfile
ports:
– “3000:80” # 本番では80番ポートで公開するなど
environment:
NODE_ENV: production
DB_HOST: db
depends_on:
db:
condition: service_healthy
restart: always # 本番では常に再起動
db:
image: postgres:14
volumes:
– db_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
healthcheck:
test: [“CMD-EXEC”, “pg_isready -U ${POSTGRES_USER}”]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
volumes:
db_data:
``
app/Dockerfile` を本番用と開発用で分けるか、ARGを使って条件分岐させるのが良いでしょう)
(この場合、
docker-compose.dev.yml
(開発環境用オーバーライド):
“`yaml
version: ‘3.8’
services:
web:
build:
context: ./app
dockerfile: Dockerfile.dev # 開発用Dockerfile (nodemon含むなど)
ports:
– “3000:3000” # 開発中は別のポートで公開
volumes:
– ./app:/usr/src/app # Bind Mount
– /usr/src/app/node_modules # node_modules の分離
environment:
NODE_ENV: development # 環境変数を開発用に上書き
DEBUG: “true” # デバッグフラグを有効化
command: [“npm”, “run”, “dev”] # 開発用コマンドに上書き
restart: on-failure # 再起動ポリシーを上書き
# DBサービスは共通設定と同じで良ければ、ここに再定義する必要はない
# もし開発用DB(例: SQLiteコンテナなど)を使いたければ、ここでdbサービス全体を上書き定義する
“`
このようにファイルを分けておき、実行時に -f
オプションで複数のファイルを指定します。リストの最後に指定したファイルの設定が、それより前に指定したファイルの設定を上書きします。
“`bash
開発環境を起動する (docker-compose.dev.yml が docker-compose.yml をオーバーライド)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d –build
特定の環境変数ファイルを指定する場合 (-f .env は不要、自動で読み込まれるが明示しても良い)
docker compose –env-file .env -f docker-compose.yml -f docker-compose.dev.yml up -d –build
“`
3. デバッグ方法
- ログの確認:
docker compose logs [サービス名]
は最も基本的なデバッグ手法です。アプリケーションやミドルウェアの起動時のエラーやランタイムエラーは、ほとんどログに出力されます。 - コンテナ内への接続:
docker compose exec [サービス名] bash
(またはsh
) でコンテナのシェルに入り、コンテナ内部の状態を確認できます。ファイルシステム構造、インストールされているパッケージ、実行中のプロセス (ps aux
)、ネットワーク設定などを調査するのに役立ちます。 - ポート衝突の確認:
docker compose up
時にポート衝突のエラーが出ないか確認します。ホストマシンの他のプロセスが使用しているポートと、Composeファイルで指定したホストポートが重複していないか確認が必要です。 - 依存関係の確認:
depends_on
の設定が正しいか確認します。ただし、前述の通りdepends_on
は起動順序しか保証しないため、アプリケーションコード側でのDB接続リトライなどは必要です。ヘルスチェック (healthcheck
とcondition: service_healthy
) を使うのがより確実です。
本番環境での考慮事項 (簡易的に)
Docker Composeは、そのシンプルさから小規模な本番環境で利用されることもありますが、基本的には開発やテスト環境に適したツールです。大規模な本番環境では、より高度なコンテナオーケストレーションツール(KubernetesやDocker Swarmなど)が一般的に使用されます。
本番環境でDocker Composeを使用する場合、以下の点に注意が必要です。
- 高可用性: Docker Compose自体にはコンテナの自動的なスケーリングや障害時の自動復旧機能はありません。コンテナがクラッシュした場合の再起動ポリシー(
restart: always
など)は設定できますが、ホストマシン自体に障害が発生した場合は対応できません。 - スケーラビリティ: Docker Composeは複数のコンテナを単一ホストまたはクラスタ上で管理する機能は限定的です。負荷に応じてサービスを自動的にスケールさせたい場合は、オーケストレーションツールが必要です。
- デプロイ: 本番環境へのデプロイプロセスを自動化する機能は組み込まれていません。CI/CDパイプラインに組み込む場合は、追加のスクリプトやツールが必要です。
- ロギング・モニタリング: 複数のコンテナのログを効率的に集約・分析したり、コンテナの状態やリソース使用率を継続的に監視したりするための機能は限定的です。専用のロギングシステム(ELK Stack, Grafana Lokiなど)やモニタリングシステム(Prometheus, Datadogなど)との連携が必要です。
- セキュリティ: 環境変数ファイルの取り扱い、イメージの脆弱性対策、コンテナのリソース制限、ネットワークセキュリティなど、本番環境特有のセキュリティ対策が必要です。
これらの理由から、本番環境ではDocker ComposeよりもKubernetesのようなオーケストレーションツールが推奨されることが多いです。しかし、小規模なアプリケーションやマイクロサービス数が少ない場合は、Docker Composeと組み合わせることで十分な場合もあります。
トラブルシューティング
Docker Composeで環境構築する際に遭遇しやすいトラブルとその解決策をいくつか紹介します。
-
コンテナが起動しない、すぐに終了してしまう:
- 原因: アプリケーションコードのエラー、依存サービスの起動待ち失敗、設定ミス(コマンド間違い、環境変数不足など)。
- 解決策: 最も重要なのは
docker compose logs [サービス名]
を確認することです。エラーメッセージから原因を特定できることが多いです。docker compose exec [サービス名] bash
でコンテナに入り、手動で起動コマンドを実行してみるのも有効です。depends_on
の設定や、依存サービスのヘルスチェックが成功しているか確認します。
-
ポート衝突:
- 原因:
docker-compose.yml
で指定したホストポートが、既にホストマシンの別のプロセス(別のDockerコンテナやローカルで実行中のアプリケーションなど)に使用されている。 - 解決策:
docker compose up
実行時にポート衝突のエラーメッセージが表示されます。netstat
やlsof
コマンド(OSによる)を使って、どのプロセスがどのポートを使用しているか確認します。衝突しているポートを別のポートに変更するか、衝突しているプロセスを停止します。
- 原因:
-
ボリュームのマウント失敗:
- 原因: ホストのパスが存在しない、Dockerがそのパスにアクセスする権限がない(特にLinuxでSELinuxなどが有効な場合)、Windows/macOSでファイル共有設定が正しくない。
- 解決策: ホスト側のパスが正確か確認します。Docker Desktopの設定で、ファイル共有が有効になっているか確認します。Linuxの場合は、SELinuxの設定やユーザー権限を確認する必要があるかもしれません。
-
サービス間の通信ができない:
- 原因: サービス名が間違っている、同じネットワークに接続されていない、ファイアウォールが通信をブロックしている。
- 解決策: 同じDocker Composeプロジェクト内のサービスは、デフォルトでサービス名をホスト名として通信できます。
docker compose exec [送信元サービス名] ping [宛先サービス名]
で疎通確認を行います。docker compose config
でネットワーク設定を確認します。コンテナ内のファイアウォール設定も確認します。
-
イメージビルド失敗:
- 原因: Dockerfileの構文エラー、Dockerfile内で指定したファイルやディレクトリが見つからない、ビルド時のネットワークエラー、依存パッケージのインストール失敗。
- 解決策:
docker compose build [サービス名]
コマンドを単独で実行し、エラーメッセージを確認します。Dockerfileの各ステップでエラーが発生した場合、その原因(パスの間違い、コマンドのタイポ、ネットワーク接続など)を調査します。
-
.env
ファイルの環境変数が読み込まれない:- 原因:
.env
ファイルがdocker-compose.yml
と同じディレクトリにない、または明示的にenv_file
で指定されていない。変数の名前が.env
ファイルとdocker-compose.yml
で一致していない。 - 解決策:
.env
ファイルが正しい場所にあるか確認します。必要であればenv_file: .env
を追加します。docker compose config
コマンドで、期待通りに環境変数が設定されているか確認します。
- 原因:
トラブルシューティングの際は、焦らずエラーメッセージをよく読み、ログを確認し、docker compose exec
でコンテナ内部の状態を調査することが解決への近道です。
まとめ
この記事では、Docker Composeを使った環境構築の基本から、docker-compose.yml
ファイルの詳細な書き方、主要なコマンドの使い方、そして実践的なサンプルプロジェクトを通じた応用までを解説しました。
Docker Composeは、複数のDockerコンテナから成るアプリケーションの定義、起動、停止、管理をYAMLファイル一つで実現できる強力なツールです。これにより、複雑だった開発環境の構築が劇的にシンプルになり、チーム間での環境の差異をなくし、再現性の高い開発ワークフローを確立できます。
特にローカル開発環境において、Webサービス、データベース、キャッシュなど、複数のミドルウェアを組み合わせた環境を簡単に立ち上げられることは、開発効率の向上に大きく貢献します。Bind Mountを活用したホストとのコード同期や、複数ファイルを使った環境ごとの設定分離といったテクニックを組み合わせることで、さらに柔軟で快適な開発環境を構築できます。
一方で、Docker Composeは主に開発・テスト用途を想定しており、本番環境で求められる高可用性、スケーラビリティ、高度な監視・運用機能などは限定的です。大規模な本番環境では、Kubernetesなどのオーケストレーションツールがより適している場合が多いことを理解しておくことも重要です。
Docker Composeは、一度マスターすれば日々の開発作業が非常に楽になるツールです。まずはシンプルな構成から試してみて、少しずつ複雑なアプリケーション構成に挑戦してみてください。この記事で紹介した内容が、皆様のDocker Composeを使った環境構築の助けとなれば幸いです。
今後の学習ステップとしては、今回触れなかった他のdocker-compose.yml
オプション(build
のキャッシュ設定、リソース制限、Capabilitesなど)、Dockerネットワークの詳細、名前付きボリュームのより高度な使い方、そして可能であればDocker SwarmやKubernetesといったオーケストレーションツールについて学ぶことで、コンテナ技術の理解をさらに深めることができるでしょう。
Happy Containerizing!