Docker Compose マルチサービスオーケストレーション:ローカル開発環境を一発起動
入社初日の午後、画面に表示された 5 つ目のエラーポップアップを見つめていました——MySQL ポートが競合、Redis バージョンが非互換、RabbitMQ がどうしても接続できない。隣の先輩が私の画面を見て、ため息をつきました。「環境構築、どれくらいかかってる?」
「午前 9 時から今まで」と、小さな声で答えました。
4 時間。データベース 3 つインストールするのに、4 時間。しかもこれはまだ始まりに過ぎず、この後 ElasticSearch と MongoDB もインストールしなければなりません。
その時初めて気づきました。ローカル開発環境、本当に落とし穴なんだと。巨大で底なしの落とし穴。
その後、チームは Docker Compose に切り替えました。新メンバーがリポジトリを clone して docker-compose up -d、たった 5 分で全サービスが起動完了。MySQL、Redis、RabbitMQ、API、Web、たった一行のコマンドで、きれいに。プロジェクト切り替え?ディレクトリを変えて、別の compose ファイルを起動するだけ。クリーンアップ?docker-compose down -v でデータボリュームごと削除、痕跡を残さず。
この記事で共有したいのは、この変化の物語です。Docker Compose でマルチサービスをオーケストレーションし、ローカル開発環境を「悪夢」から「一行のコマンドで済むこと」に変える方法を。
なぜマルチサービスオーケストレーションが必要なのか
正直に言うと、10 年前にモノリシックアプリを作っていた頃は、環境設定なんて簡単でした——JDK を入れて、データベース接続文字列を設定すれば、プロジェクトが動く。でも今は違います。
ほとんどのプロジェクトがマイクロサービスアーキテクチャに分割され、あるいは少なくともフロントエンドとバックエンドが分離されています。典型的なローカル開発環境には、最低限これらが必要です。フロントエンド Web サービス、バックエンド API サービス、MySQL でビジネスデータ保存、Redis でキャッシュと Session 管理、RabbitMQ で非同期メッセージ処理。プロジェクトによっては、ElasticSearch で検索機能、MongoDB でログ保存を追加することも。
ここで問題が生じます。
手動インストールの苦痛
各マシンで毎回インストールする必要があります。MySQL はバージョンを選ばなければなりません——5.7 か 8.0 か?間違ったバージョンをインストールすると、SQL 構文が非互換。Redis はポート設定——デフォルトは 6379、でも他のサービスに占有されていたら?RabbitMQ には Erlang ランタイムのインストールも必要です…RabbitMQ のインストールチュートリアルだけで、30 分は見ていました。
インストール後も、バージョン競合の問題があります。ローカルで他のプロジェクトを実行したことがあり、MySQL に旧バージョンのデータが残っている。ポート競合で、サービス起動に失敗。半日トラブルシューティングして、やっとゾンビプロセスが完全に kill されていないことに気づく。
最も絶望的なのはプロジェクト切り替えです。プロジェクト A を終えて、プロジェクト B に切り替える。プロジェクト A の MySQL が 3306 を占有、プロジェクト B も 3306 を使いたい。設定ファイルを変更するか、プロジェクト A のサービスを停止するか。設定を変えて、数日後にまたプロジェクト A に戻ると、設定をまた戻さなければならない。
行ったり来たりの悪循環。
チームコラボレーションの悪夢
「私の環境では動くのに」
この言葉、どのチームでも聞いたことがあるでしょう。新メンバーが入社して、コードを clone して、環境をインストールして、動かない。なぜ?旧プロジェクトの MySQL バージョンは 5.7、新メンバーは 8.0 をインストール。旧プロジェクトの Redis はパスワードなし、新メンバーのローカル Redis はパスワード設定済み。設定ファイルを数箇所変更しても、まだ動かない。
最終的に先輩が来て手伝い、二人で半日かけて、やっと環境を整えました。
丸一日が、こうして無駄になりました。
Compose の解決アプローチ
Docker Compose のアプローチはシンプルです。すべてのサービスをコンテナにパッケージ化し、一枚の設定ファイルで一元管理します。
各データベースのインストール方法、バージョン設定、ポート割り当てを気にする必要がありません。Compose ファイルに明記すれば、ワンクリックで起動し、すべてのサービスが設定通りに実行されます。プロジェクト切り替え?ディレクトリを変えて、別の Compose ファイルを起動するだけ。クリーンアップ?一行のコマンドですべてのコンテナとボリュームを削除。
これは「自分でパソコンを組み立てる」から「完成品を買う」への変化に似ています。メモリの挿し方やグラフィックボードの電源ケーブル接続を気にする必要がありません——メーカーがすべて用意してくれて、電源を入れれば使える。
docker-compose.yml の核心設定
まず、完全な設定ファイルを見てみましょう。プロジェクトには 4 つのサービスがあるとします。Web フロントエンド、API バックエンド、MySQL データベース、Redis キャッシュ。
# docker-compose.yml
version: "3.8" # Compose ファイルバージョン、3.8 はほとんどの設定オプションをサポート
services:
# フロントエンド Web サービス
web:
build: ./frontend # ローカルの frontend ディレクトリからイメージをビルド
ports:
- "3000:3000" # ホスト 3000 -> コンテナ 3000
depends_on:
- api # api サービスに依存、api が先に起動
environment:
- API_URL=http://api:8080 # フロントエンドがバックエンドにアクセスするアドレス
# バックエンド API サービス
api:
build: ./backend # ローカルの backend ディレクトリからイメージをビルド
ports:
- "8080:8080"
depends_on:
- mysql
- redis # データベースとキャッシュに依存
environment:
- DB_HOST=mysql # データベースアドレス(コンテナ名)
- DB_PORT=3306
- DB_USER=root
- DB_PASSWORD=dev123 # 開発環境パスワード、本番環境では .env ファイルを使用
- REDIS_HOST=redis
- REDIS_PORT=6379
# MySQL データベース
mysql:
image: mysql:8.0 # 公式イメージを直接使用、ビルドなし
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=dev123
- MYSQL_DATABASE=myapp # データベースを自動作成
volumes:
- mysql_data:/var/lib/mysql # データをボリュームに永続化
# Redis キャッシュ
redis:
image: redis:7-alpine # alpine バージョンはより軽量
ports:
- "6379:6379"
volumes:
mysql_data: # データボリュームを定義、MySQL データを永続化保存
核心フィールドの解説
services の下にすべてのサービスを定義します。各サービスには 3 つのソースがあります。build でローカルコードからビルド、image で公式イメージを直接プル、または両者を組み合わせる(ローカルビルド + 特定のイメージをベースに)。
ports でポートマッピングを行います。フォーマットは "ホストポート:コンテナポート"。上記の設定では、Web は 3000、API は 8080、MySQL は 3306、Redis は 6379 にマッピング。ローカルポートが占有されている場合、ホストポートを変更できます。例えば "13006:3306" とすれば、localhost:13006 で MySQL に接続できます。
depends_on で起動順序を制御します。MySQL と Redis が先に起動し、その後 API(これらに依存)、最後に Web(API に依存)。ただし落とし穴があります、後で説明します。
environment で環境変数を設定します。データベースパスワード、接続アドレス、ポート番号をここで設定可能。注意点として、本番環境ではパスワードをここに書かず、.env ファイルまたは環境変数インジェクションを使用します。
volumes でデータ永続化を行います。MySQL のデータを mysql_data ボリュームに保存、これでコンテナを削除してもデータは失われません。次回起動時もデータは残っています。
よくある落とし穴
コンテナ名がサービス名になります。上記の設定で、API サービスがデータベースに接続する際、DB_HOST=mysql を使用し、localhost ではありません。なぜでしょうか?
各コンテナは独立したネットワーク環境を持つからです。localhost は API コンテナ内では API コンテナ自身を指し、ホストマシンでも MySQL コンテナでもありません。Compose は内部ネットワークを自動作成し、サービス間はサービス名で互いにアクセスします。mysql という名前が、ネットワーク内での MySQL コンテナのアドレスになります。
初めて Compose ファイルを書いた時、私はこの落とし穴にハマりました——localhost:3306 と書いて、どうしてもデータベースに接続できませんでした。後で、コンテナ名を使う必要があることに気づきました。
サービス依存関係と起動順序
depends_on はシンプルに見えます。MySQL が先に起動し、API がその後起動。しかし実際には微妙な問題があります。
Compose の depends_on はコンテナの起動順序のみを保証し、サービスの準備完了順序は保証しません。言い換えると、MySQL コンテナは起動したが、MySQL サービスはまだ接続を受け付ける準備ができていない——データベースを初期化中、設定をロード中、ポートリスニングを起動中。この時 API サービスが接続しに行くと、高確率で失敗します。
私はこの状況に遭遇したことがあります。docker-compose up の後、API が即座にエラーを報告:データベース接続失敗。10 秒後に再試行すると、成功。理由は MySQL コンテナは起動したが、MySQL サービスはまだ準備ができていなかったからです。
解決策 1:ヘルスチェック
Compose は設定内にヘルスチェックを追加できます。ヘルスチェックが通過した後のみ、依存するサービスが起動します。
services:
mysql:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s # 5 秒ごとにチェック
timeout: 3s # タイムアウト時間
retries: 10 # 10 回失敗で unhealthy と判定
# ... その他の設定
api:
depends_on:
mysql:
condition: service_healthy # MySQL ヘルスチェック通過を待機
MySQL は mysqladmin ping コマンドを実行し、自分が接続を受け付けられるかをチェックします。5 秒ごとにチェックし、最大 10 回待機(50 秒)。ヘルスチェックが通過して初めて、API が起動します。
この方法は有効ですが、欠点があります。各サービスにヘルスチェック設定を書く必要があります。一部の公式イメージ(Redis など)は便利なヘルスチェックコマンドを提供していないため、自分で考える必要があります。
解決策 2:アプリ層リトライ
より簡単な方法は、アプリケーションコードにリトライロジックを追加することです。接続失敗したら、数秒待って再試行。MySQL の起動が遅くても、準備できるのを待てばいい。
指数バックオフ(Exponential Backoff)はよく使われる戦略です。最初は 1 秒待機、次は 2 秒、その次は 4 秒…待機時間を徐々に増やします。ほとんどのデータベースは 30 秒以内に準備完了します。
Node.js の場合、mysql2 の接続プール設定を使用できます:
const pool = mysql.createPool({
host: 'mysql',
port: 3306,
user: 'root',
password: 'dev123',
database: 'myapp',
waitForConnections: true, // 接続が利用可能になるまで待機
connectionLimit: 10,
queueLimit: 0,
});
Python の場合、tenacity ライブラリでリトライします:
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=10))
def connect_db():
return mysql.connector.connect(host='mysql', ...)
2 つのアプローチの選択
ヘルスチェックはより正確です——データベースが本当に準備完了した場合のみ、API が起動。ただし設定は少し面倒で、各データベースに対応するチェックコマンドを書く必要があります。
アプリ層リトライはよりシンプル——コードに数行追加するだけで、Compose 設定を変更する必要がありません。欠点は API 起動後、しばらくリトライが続き、ログに接続失敗のエラーが表示されること(最終結果には影響しません)。
個人的にはアプリ層リトライを好みます。より省力で、ほとんどの場合正常に動作するからです。ヘルスチェックは予備として、特に起動が遅いサービスに使用します。
マルチ環境設定戦略
ローカル開発、テスト環境、本番環境では、設定が通常異なります。例えばポートマッピング——開発環境ではデータベースポートを公開し、ローカルデバッグを容易に。本番環境では不要、データベースはコンテナ内部ネットワークでのみアクセス。
これらの設定を一枚のファイルに書くと、環境切り替えのたびにファイルを変更し、また元に戻す必要があります。面倒だし、間違いやすい。
Compose にはこの問題を解決するデザインがあります。ベースファイル + オーバーライドファイル。
ベースファイル:共通設定
docker-compose.yml にすべての環境で共有する設定を書きます——サービス定義、イメージバージョン、内部ネットワーク、データボリューム。
# docker-compose.yml(ベース設定)
version: "3.8"
services:
web:
build: ./frontend
# ports は書かない、オーバーライドファイルで補足
api:
build: ./backend
environment:
- DB_HOST=mysql
- REDIS_HOST=redis
# ports は書かない
mysql:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
# ports は書かない、本番環境では公開不要
redis:
image: redis:7-alpine
volumes:
mysql_data:
ポートマッピングは書いていません、異なる環境でポート設定が異なるためです。
開発環境オーバーライドファイル
docker-compose.override.yml で開発環境特有の設定を補足——ポートマッピング、開発用パスワード、デバッグ用環境変数。
# docker-compose.override.yml(開発環境)
version: "3.8"
services:
web:
ports:
- "3000:3000" # 開発環境でポートを公開、ローカルアクセスを容易に
api:
ports:
- "8080:8080"
environment:
- DEBUG=true # 開発環境でデバッグモードを有効化
mysql:
ports:
- "3306:3306" # 開発環境でデータベースポートを公開、接続デバッグを容易に
environment:
- MYSQL_ROOT_PASSWORD=dev123 # 開発環境のシンプルなパスワード
redis:
ports:
- "6379:6379"
Compose にはデフォルトの動作があります。docker-compose up 実行時、docker-compose.yml と docker-compose.override.yml を自動的にマージ。2 つのファイルの設定が重なり、override の設定がベース設定を上書き。
そのためローカル開発では、docker-compose up を直接実行すれば、開発環境の完全な設定が取得できます。
本番環境オーバーライドファイル
docker-compose.prod.yml で本番環境の設定を補足——ポートを公開しない、本番パスワードを使用、外部サービスに接続。
# docker-compose.prod.yml(本番環境)
version: "3.8"
services:
web:
# ポートを公開しない、リバースプロキシ(nginx)経由でアクセス
api:
environment:
- DB_HOST={{DB_HOST}} # 環境変数から読み込み、ファイルにパスワードを書かない
- DB_PASSWORD={{DB_PASSWORD}}
mysql:
# ポートを公開しない、外部から直接接続不可
environment:
- MYSQL_ROOT_PASSWORD={{MYSQL_ROOT_PASSWORD}}
本番環境起動時、-f パラメータで設定ファイルを指定します:
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
2 つのファイルが重なり、prod の設定がベース設定を上書き。データベースポートは公開されず、パスワードは環境変数から読み込まれます。
環境変数インジェクション
本番環境のパスワードはファイルに書くべきではありません。Compose は .env ファイルまたはシステム環境変数から値を読み込めます。
# .env ファイル(git にコミットしない)
DB_HOST=prod-mysql.internal
DB_PASSWORD=super_secret_password_123
MYSQL_ROOT_PASSWORD=another_secret
設定ファイル内で {{VAR:-default}} 構文を使用:
environment:
- DB_HOST={{DB_HOST:-localhost}} # DB_HOST が未設定の場合、localhost を使用
- DB_PASSWORD={{DB_PASSWORD:-dev123}}
.env ファイルはバージョン管理にコミットしないでください。.env.example にサンプル値を書き、チームメンバーはコピーして実際の設定を埋めます。
ワンコマンド実践
設定ができたら、次は起動、停止、デバッグ。これらのコマンドをマスターすれば、日常操作はほぼ十分です。
全サービス起動
docker-compose up -d
up ですべてのサービスを起動。-d はバックグラウンド実行(detached mode)を意味し、ターミナルを占有しません。-d なしでは、すべてのサービスのログがターミナルに直接出力され、Ctrl+C で停止できます。
起動後、Compose はイメージをプルし(image 使用時)、イメージをビルドし(build 使用時)、コンテナを作成し、サービスを起動。初回起動はイメージのプルが必要なため遅くなります。次回以降の起動は高速です、イメージが既に存在するため。
サービス状態確認
docker-compose ps
すべてのコンテナの実行状態を一覧表示。出力はこのようになります:
NAME COMMAND SERVICE STATUS PORTS
myapp-web-1 "npm start" web running 0.0.0.0:3000->3000/tcp
myapp-api-1 "node index.js" api running 0.0.0.0:8080->8080/tcp
myapp-mysql-1 "mysqld" mysql running 0.0.0.0:3306->3306/tcp
myapp-redis-1 "redis-server" redis running 0.0.0.0:6379->6379/tcp
STATUS が running であれば正常に実行中。exited や error と表示されている場合、サービス起動に失敗しています。
サービスログ確認
docker-compose logs -f api
API サービスのログを確認。-f は継続追跡(follow)を意味し、新しいログがリアルタイムで表示。-f なしでは既存のログのみ表示。
サービス名なしで docker-compose logs -f とすると、すべてのサービスのログが表示されますが、ログ量が多いと見づらくなります。
停止とクリーンアップ
docker-compose down
すべてのコンテナを停止し、コンテナとネットワークを削除。ただしデータボリュームは削除されません——MySQL のデータは残ります。
完全にクリーンアップする場合、データボリュームを含める:
docker-compose down -v
-v でデータボリュームを削除。次回起動時、MySQL は再初期化され、データはすべてクリア。デバッグ時によく使うコマンド——データが壊れたら、クリアしてやり直せます。
イメージ再ビルド
コードを変更したら、イメージを再ビルド:
docker-compose build api
API サービスのイメージのみをビルド。ビルド後、コンテナを再起動:
docker-compose up -d api
または一発で、ビルドして再起動:
docker-compose up -d --build api
--build でイメージが存在しても強制的に再ビルド。
主要コマンド速習表
| コマンド | 作用 |
|---|---|
docker-compose up -d | バックグラウンドで全サービス起動 |
docker-compose ps | 実行状態確認 |
docker-compose logs -f api | API ログ確認 |
docker-compose down | コンテナを停止・削除 |
docker-compose down -v | コンテナとデータボリュームを停止・削除 |
docker-compose restart api | API サービス再起動 |
docker-compose build api | API イメージ再ビルド |
これらのコマンドで日常操作の 90% をカバー。その他のコマンド(exec、cp、top)は必要な時にドキュメントを確認。
Docker Compose マルチサービスオーケストレーション実践
Docker Compose で Web、API、MySQL、Redis の 4 つのサービスをオーケストレーションし、ローカル開発環境を一発起動を実現
⏱️ 目安時間: 15 分
- 1
ステップ1: docker-compose.yml ファイル作成
プロジェクトルートに設定ファイルを作成:
```yaml
version: "3.8"
services:
web:
build: ./frontend
ports: ["3000:3000"]
depends_on: [api]
api:
build: ./backend
ports: ["8080:8080"]
depends_on: [mysql, redis]
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: dev123
MYSQL_DATABASE: myapp
redis:
image: redis:7-alpine
```
注意:コンテナ間通信ではサービス名(DB_HOST=mysql など)を使用、localhost ではない - 2
ステップ2: 全サービス起動
docker-compose.yml があるディレクトリで実行:
```bash
docker-compose up -d
```
• 初回起動はイメージをプルするため時間がかかる
• 2 回目以降は既存イメージを使用、数秒で完了
• -d なしではターミナルがログ表示で占有される - 3
ステップ3: サービス実行状態確認
すべてのコンテナが正常起動したか確認:
```bash
docker-compose ps
```
• STATUS が running なら正常
• exited や error の場合、logs で調査:
```bash
docker-compose logs api
``` - 4
ステップ4: マルチ環境設定(オプション)
docker-compose.override.yml を作成(開発環境):
```yaml
version: "3.8"
services:
mysql:
ports: ["3306:3306"]
```
• Compose は自動的に override ファイルをマージ
• 本番環境では -f で指定:
```bash
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
``` - 5
ステップ5: 環境クリーンアップ
すべてのコンテナを停止・削除:
```bash
docker-compose down # データボリュームを保持
docker-compose down -v # データボリュームを削除(データクリア)
```
• プロジェクト切り替え時は down でクリーンアップ
• データが壊れたらやり直したい時は down -v
まとめ
従来の方法と Compose の方法の効率を比較してみましょう:
| 操作 | 従来の方法 | Compose の方法 |
|---|---|---|
| 新メンバーの環境構築 | 4-8 時間 | 5 分(clone + up) |
| プロジェクト切り替え | 設定変更、サービス停止、再起動 | ディレクトリ切り替え、別の compose 起動 |
| 環境クリーンアップ | 手動アンインストール、残存プロセス調査 | 一行のコマンドでコンテナとボリューム削除 |
| チーム環境の一致性 | 各マシンで異なる可能性 | 設定ファイルで統一、環境は完全に一致 |
差は歴然としています。
まだ手動でデータベースをインストールし、設定ファイルを変更し、ポート競合を調査しているなら、Docker Compose を試してみてください。まずシンプルなプロジェクトから——API 1 つ + MySQL 1 つ。docker-compose.yml を 1 枚書いて、動かしてみる。慣れてきたら、Redis、RabbitMQ を追加し、マルチ環境設定を行いましょう。
チームで導入する場合、docker-compose.yml と docker-compose.override.yml をリポジトリにコミットし、README に起動手順を追加。新メンバーはコードを clone して、一行のコマンドで、開発環境の準備完了。
これは「環境設定ドキュメント」を書くよりはるかに信頼性が高い。ドキュメントは陳腐化するが、設定ファイルは陳腐化しない。
FAQ
docker-compose.yml と Dockerfile の違いは?
depends_on でサービスの準備完了を保証できる?
コンテナ内からホストマシンのサービスにアクセスするには?
データはどこに保存される?コンテナを削除してもデータは失われる?
マルチ環境設定(dev/test/prod)の管理方法は?
ポートが占有されている場合は?
7 min read · 公開日: 2026年4月9日 · 更新日: 2026年4月9日
関連記事
n8n 実践ガイド:Webhook トリガーと IF/Switch 条件分岐の設計
n8n 実践ガイド:Webhook トリガーと IF/Switch 条件分岐の設計
Supabase Storage 実践ガイド:ファイルアップロード、権限制御とCDN活用
Supabase Storage 実践ガイド:ファイルアップロード、権限制御とCDN活用
GitHub Actions Matrix ビルド:マルチバージョン並列テストの実践

コメント
GitHubアカウントでログインしてコメントできます