はじめに
Webアプリとデータベースを Docker Compose で動かすときに押さえておきたい3つのテーマを整理しました。
- 起動順と待ち合わせ(
depends_on/healthcheck) - 設定の外出し(
.envとenv_file) - ネットワークとボリューム
題材は、店舗の注文を管理するアプリです。コンテナは db(PostgreSQL)、web(Web API)、worker(定期ジョブ)、migrate(テーブルを作って終了する初期化処理)の4つです。
第1章 起動順と待ち合わせ
depends_on は「コンテナの起動」しか待たない
docker compose up -d
│
├─ db を起動 ───────────────────────────────┐
│ healthcheck: pg_isready を5秒ごとに実行 │ 状態: starting
│ ▼ 状態: healthy(接続を受け付けられる)
│
├─ web / worker depends_on: - db
│ → db コンテナが「起動した」ことだけを待つ
│
└─ migrate depends_on: db: condition: service_healthy
→ db が healthy になるまで待ってから起動する
depends_on: - db は起動順の指定で、中のPostgreSQLが接続を受け付けられるかまでは見ません。接続できるまで待たせたいときは、db に healthcheck を付け、待つ側に condition: service_healthy を書きます。
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 5s # 5秒ごとに確かめる
timeout: 3s # 1回の確認が3秒で終わらなければ失敗と数える
retries: 12 # 12回続けて失敗したら unhealthy と判定する
start_period: 90s # 起動から90秒間は、失敗しても回数に数えない
retries は回数です。start_period が無いと、約60秒(5秒 × 12回)で起動しなければ unhealthy になり、待っていたコンテナは起動されません。start_period の間に成功すればその時点で healthy になるので、長めに取っても起動は遅くなりません。
healthy 待ちが必要なのは「1回で終わる処理」だけ
depends_on が効くのは起動時だけで、運用中にDBが止まった場合は何もしません。そのため、常駐プロセスはどのみち「接続に失敗したらログを残し、次の処理でもう一度試す」作りにしておく必要があります。そう作れば起動直後の接続失敗にも同じ処理で対応できるので、healthy 待ちは要りません。
| 接続に失敗したら | healthy 待ち | |
|---|---|---|
| web・worker(常駐) | 次の処理でもう一度試す | 不要 |
| migrate(1回で終了) | 次がない。失敗がそのまま結果になる | 必要 |
第2章 設定の外出し
環境変数は2段階で使われる
docker compose up -d
│
│ 【段階1】Compose が docker-compose.yml を組み立てる
│ .env を自動で読み、${...} の部分を値で置き換える
│
│ 【段階2】コンテナを起動し、環境変数を渡す
│ env_file に並べたファイルの中身を、コンテナの環境変数にする
▼
コンテナの中: os.environ["DATABASE_URL"] で読める
| ファイル | 誰が読むか | 何に使うか |
|---|---|---|
.env |
Compose(段階1) |
${...} の置き換え |
.env.development など |
コンテナ(段階2) | アプリの設定 |
.env.example |
人間 | 変数の一覧(ダミー値)。Gitで管理するのはこれだけ |
${...} の書き方
${ENVIRONMENT} # そのまま置き換える。未設定なら空文字
${ENVIRONMENT:-development} # 未設定なら development を使う
${POSTGRES_PASSWORD:?メッセージ} # 未設定なら、メッセージを出して止める
パスワードには :? を使います。:- で既定値を置くと、その値が docker-compose.yml ごとGitに入り、設定し忘れても起動してしまいます。
環境の切り替えを1か所にまとめる
env_file:
- .env # 共通の設定(先に読む)
- .env.${ENVIRONMENT:-development} # 環境ごとの設定(後に読む → 同じ名前ならこちらが勝つ)
.env の ENVIRONMENT を1行変えるだけで、開発と本番を切り替えられます。
第3章 ネットワークとボリューム
【コンテナ → コンテナ】ports は不要
web ── "db:5432" ──▶ Docker内のDNS(サービス名をIPに変換)──▶ db
【ホスト → コンテナ】ports が必要
ホスト ── 127.0.0.1:5432 ──▶ ports: "127.0.0.1:5432:5432" ──▶ db:5432
【データの保存先】volumes
db /var/lib/postgresql/data ──▶ 名前付きボリューム(Dockerが管理する置き場)
web /workspace ──▶ ホストのプロジェクトフォルダ(バインドマウント)
接続先はサービス名で書く
同じネットワークにいるコンテナには、サービス名で接続できます。コンテナの中の localhost はそのコンテナ自身を指すため、DBの接続先は localhost ではなく db です。IPアドレスはDockerが自動で割り振り、作り直すと変わるので、直接書きません。
$ docker compose exec web pg_isready -h db -p 5432
db:5432 - accepting connections
$ docker compose exec web pg_isready -h localhost -p 5432
localhost:5432 - no response
ports はホストから入るための入口
ports はホストからコンテナへ入るための設定で、コンテナどうしの通信には使いません。"127.0.0.1:5432:5432" と書くと、ホスト自身からだけ接続を受け付け、LAN側からは接続できません。
名前付きボリュームとバインドマウント
| 名前付きボリューム | バインドマウント | |
|---|---|---|
| 書き方の例 | pgdata:/var/lib/postgresql/data |
.:/workspace |
| 実体の場所 | Dockerが管理する置き場 | ホストの指定したフォルダそのもの |
| 向いているもの | DBのデータ(プログラムだけが触る) | ソースコード(人が編集し、Gitで管理する) |
| 消える条件 | docker compose down -v |
ホストでフォルダを消したとき |
バインドマウントしたソースを編集すると、コンテナの中のファイルもすぐに変わります。ただし、動いているプロセスは古いコードを読み込んだままなので、再起動が必要です。
完成形
はじめに挙げた4つのコンテナで組んだ例です。web・worker・migrate は同じイメージを使い、command と待ち方だけを変えています。
services:
db:
image: postgres:16
environment:
POSTGRES_USER: shop
POSTGRES_DB: shop
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?POSTGRES_PASSWORD を .env に設定してください}"
healthcheck:
test: ["CMD", "pg_isready", "-U", "shop", "-d", "shop"]
interval: 5s
timeout: 3s
retries: 12
start_period: 90s
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "127.0.0.1:5432:5432"
restart: unless-stopped
networks:
- backend-net
web:
build: .
working_dir: /workspace
command: ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
env_file:
- .env.${ENVIRONMENT:-development}
depends_on:
- db # 常駐するので起動順だけ指定する
volumes:
- .:/workspace
ports:
- "127.0.0.1:8000:8000"
restart: unless-stopped
networks:
- backend-net
worker:
build: .
working_dir: /workspace
command: ["python", "-m", "app.worker"]
env_file:
- .env.${ENVIRONMENT:-development}
depends_on:
- db # 常駐するので起動順だけ指定する
volumes:
- .:/workspace
restart: unless-stopped
networks:
- backend-net
migrate:
build: .
working_dir: /workspace
command: ["python", "-m", "app.migrate"]
env_file:
- .env.${ENVIRONMENT:-development}
depends_on:
db:
condition: service_healthy # 1回で終わるので、DBが接続を受け付けるまで待つ
volumes:
- .:/workspace
restart: "no" # 終了したら再起動しない
networks:
- backend-net
networks:
backend-net:
volumes:
pgdata:
ENVIRONMENT=development
POSTGRES_PASSWORD=change-me
DATABASE_URL=postgresql://shop:change-me@db:5432/shop
付録 よく使うコマンド
| コマンド | 何をするか |
|---|---|
docker compose up -d |
全サービスをバックグラウンドで起動する |
docker compose restart web |
指定したサービスだけ再起動する |
docker compose down |
コンテナとネットワークを削除する。ボリュームは残る |
docker compose down -v |
ボリュームも削除する。DBの中身が消える |
docker compose ps |
コンテナの状態(healthy など)を一覧する |
docker compose logs -f --tail 50 web |
末尾50行からログを流し続ける |
docker compose exec web bash |
コンテナに入ってシェルを開く |
docker compose exec db psql -U shop -d shop |
DBに入ってSQLを打つ |
docker compose config -q |
書式が正しいかだけを確かめる |