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、非同期メッセージを処理する RabbitMQ。プロジェクトによっては、検索用に ElasticSearch、ログ保存に MongoDB も加わります。
ここで問題が出てきます。
手動インストールの苦しみ
どのマシンにも一通りインストールしなければなりません。MySQL はバージョンを選ぶ必要があります。5.7 か 8.0 か?間違えると SQL の文法が合いません。Redis はポートを設定する必要があります。デフォルトは 6379 ですが、別のサービスに占有されていたら?RabbitMQ はさらに Erlang のランタイムも必要です……RabbitMQ のインストール手順を読むだけで、私は 30 分かかりました。
インストールが終わっても、バージョン衝突の問題が残ります。ローカルで別のプロジェクトを動かしたことがあると、MySQL に旧バージョンのデータが残っています。ポートが競合し、サービスの起動に失敗します。半日かけて調べたら、ゾンビプロセスが片付いていなかったのが原因でした。
最もつらいのはプロジェクトの切り替えです。プロジェクト A を終えてプロジェクト B に切り替えるとき、プロジェクト A の MySQL が 3306 を占有していて、プロジェクト B も 3306 を使いたい。設定ファイルを書き換えるか、プロジェクト A のサービスを止めるしかありません。設定を直しても、数日後にまたプロジェクト A に戻ると、設定をまた書き換える羽目になります。
何度も行ったり来たりです。
チーム協業の悪夢
「自分の PC では動くんだけど。」
このセリフ、どのチームでも聞いたことがあるでしょう。新メンバーが入社し、コードを clone し、環境を構築しても、動かない。なぜか?古いプロジェクトの MySQL は 5.7 なのに、新メンバーは 8.0 を入れた。古いプロジェクトの Redis はパスワードなしなのに、新メンバーのローカル Redis はパスワードを設定している。設定ファイルを何カ所も直しても、まだ動きません。
最後は先輩が手伝いに来て、2 人で半日近くかけて、ようやく環境が通りました。
この 1 日が、そうやって消えていくのです。
Compose の解決アプローチ
Docker Compose の発想はとてもシンプルです。すべてのサービスをコンテナにパッケージし、1 つの設定ファイルでまとめて管理する。
各データベースをどうインストールするか、バージョンをどう設定するか、ポートをどう割り当てるかを気にする必要はありません。Compose ファイルに書いておけば、ワンコマンドで起動し、すべてのサービスが設定どおりに動きます。プロジェクトの切り替え?ディレクトリを移って、別の Compose ファイルを起動するだけ。後始末は?1 行のコマンドですべてのコンテナとボリュームを削除します。
これは「自分で PC を組み立てる」から「完成品を買う」へと変わるようなものです。メモリの挿し方も、グラフィックボードへの電源ケーブルの繋ぎ方も気にする必要はありません。メーカーがすでにやってくれていて、電源を入れればすぐ使えるのです。
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 ではありません。なぜでしょう?
それは、各コンテナが独立したネットワーク環境だからです。API コンテナ内の localhost は 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 は設定に健全性チェック(healthcheck)を追加できます。健全性チェックが通った後でないと、それに依存するサービスは起動しません。
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 の起動が遅いなら、準備できるまで待てばいいのです。
指数バックオフはよく使われる戦略です。1 回目は 1 秒待ち、2 回目は 2 秒、3 回目は 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 起動後にしばらくリトライを繰り返し、ログに接続失敗のエラーが出ること(最終結果には影響しませんが)です。
私個人はアプリ層のリトライの方が好みです。手軽ですし、ほとんどの場合は問題なく動くからです。健全性チェックは予備として、起動が特に遅いサービスに使います。
マルチ環境の設定戦略
ローカル開発、テスト環境、本番環境では、設定が通常異なります。たとえばポートマッピングです。開発環境ではローカルでのデバッグのためにデータベースのポートを公開する必要がありますが、本番環境では不要で、データベースはコンテナ内部ネットワークからのみアクセスします。
これらの設定をすべて 1 つのファイルに書くと、環境を切り替えるたびにファイルを書き換え、また戻す必要があります。面倒ですし、間違えやすくもあります。
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 を使う場合)、コンテナの作成、サービスの起動を行います。初回起動はイメージを取得するため遅めです。2 回目以降は、イメージがすでに存在するため速くなります。
サービスの状態を確認する
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)は、必要になったときにドキュメントを調べればよいでしょう。
おわりに
従来の方式と Compose 方式の効率を比べてみましょう。
| 操作 | 従来の方式 | Compose 方式 |
|---|---|---|
| 新メンバーの環境構築 | 4〜8 時間 | 5 分(clone + up) |
| プロジェクト切り替え | 設定変更・サービス停止・再起動 | ディレクトリ移動・別の compose を起動 |
| 環境のクリーンアップ | 手動アンインストール・残留プロセスの調査 | 1 行でコンテナとボリュームを削除 |
| チーム環境の一貫性 | マシンごとに異なる可能性 | 設定ファイルが統一され、環境が完全に一致 |
差は歴然です。
もしまだ手動でデータベースを入れ、設定ファイルを書き換え、ポート競合を調べているなら、Docker Compose を試してみてください。まずはシンプルなプロジェクトから始めましょう。API 1 つ + MySQL 1 つ。1 つの docker-compose.yml を書いて、動かしてみます。慣れてきたら、Redis、RabbitMQ を加え、マルチ環境設定を行います。
チーム内で広めるなら、docker-compose.yml と docker-compose.override.yml をリポジトリにコミットし、起動手順を説明する README を添えます。新メンバーが入社したら、コードを clone して、ワンコマンドで開発環境が整います。
これは「環境構築ドキュメント」を書くよりずっと確実です。ドキュメントは古くなりますが、設定ファイルは古くなりません。
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 を使います
FAQ
docker-compose.yml と Dockerfile の違いは何ですか?
depends_on でサービスの準備完了は保証されますか?
コンテナ内からホストマシンのサービスにアクセスするには?
データはどこに保存される?コンテナを削除するとデータは消える?
マルチ環境設定(dev/test/prod)はどう管理する?
ポートが占有されている場合はどうする?
6分で読めます · 公開日: 2026年4月9日 · 更新日: 2026年6月8日
Docker 実践ガイド
検索からこのページに来た場合は、前後の記事もあわせて読むと同じテーマの理解がかなり早く深まります。
前の記事
Docker マルチステージビルド実践:本番イメージを 1GB から 10MB へ
Docker マルチステージビルドの実践テクニックを習得し、本番イメージを 1GB から 10MB まで削減。Go・Node.js・Python の 3 言語テンプレート、Alpine と Distroless の比較、よくある 5 つの落とし穴の回避策を解説します。
第 5 / 37 記事
次の記事
Docker Compose 本番デプロイの三要素:ヘルスチェック、再起動ポリシー、リソース制限
Docker Compose 本番デプロイの三要素を詳しく解説。サービスの準備完了を判断するヘルスチェック、自動復旧を実現する再起動ポリシー、暴走を防ぐリソース制限。完全な YAML 設定テンプレート付きで、安定したコンテナ化アプリケーションを構築できます。
第 7 / 37 記事
関連記事
Dockerfile入門:ゼロから最初の Docker イメージを作る(実例付き)
Dockerfile入門:ゼロから最初の Docker イメージを作る(実例付き)
Docker vs 仮想マシン:5分で理解する性能差とシーン別選び方ガイド
Docker vs 仮想マシン:5分で理解する性能差とシーン別選び方ガイド
Docker インストールの落とし穴ガイド 2025:permission denied から正常起動までの完全解決策
コメント
GitHubアカウントでログインしてコメントできます