Linux & IT ノート

Docker Compose で複数コンテナを管理する:1ファイルで環境を再現

管理人 約9分で読めます

前回まででボリュームとネットワークの基礎を学びました。 コンテナを使いこなせるようになってくると、次第にある苦痛が表れてきます。コマンドが長い、複数コンテナを起動するたびに docker run を何度も打たなければならない、チームメンバーに「このコマンドをこの順番で実行して」と伝えるのが面倒——というやつです。

Docker Compose(ドッカーコンポーズ)は、そういった煩雑さを解消するツールです。アプリ・DB・キャッシュサーバーなどの複数コンテナを1つの YAML ファイルに定義し、コマンド1つで一括起動・停止できます。Docker Desktop には同梱されており、Linux でも Docker Engine と一緒に導入できます。

compose.yaml の基本構造

Compose の設定ファイルは compose.yaml(従来は docker-compose.yml)という名前で書きます。どちらの名前でも動作しますが、現在の公式仕様では compose.yaml が推奨されています。

ファイルの最上位にある services キーの下に、起動するコンテナをサービスとして列挙していく構造です。

services:
  サービス名:
    image: イメージ名:タグ     # 使うイメージ(または build で Dockerfile を指定)
    build: .                   # Dockerfile からビルドする場合
    ports:
      - "ホスト:コンテナ"      # ポートマッピング
    volumes:
      - ボリューム名:パス      # ボリュームのマウント
    environment:
      KEY: value               # 環境変数
    depends_on:
      - 他のサービス名         # 依存するサービス(起動順の制御)
    networks:
      - ネットワーク名

volumes:
  ボリューム名:                # トップレベルで名前付きボリュームを宣言する

networks:
  ネットワーク名:              # トップレベルでネットワークを宣言する(省略可)

build: . を指定したサービスは、そのディレクトリの Dockerfile を使ってイメージをビルドしてから起動します。image:build: は排他なので、どちらか一方を使います。

実例:Node.js アプリ + PostgreSQL を1ファイルで定義する

前回まで docker run を繰り返して起動していた Node.js アプリと PostgreSQL の構成を、compose.yaml に書き直してみます。

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      DATABASE_URL: postgres://postgres:secret@db:5432/mydb
    depends_on:
      - db
    networks:
      - mynet

  db:
    image: postgres:16
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: mydb
    networks:
      - mynet

volumes:
  db-data:

networks:
  mynet:

app サービスの DATABASE_URL に注目してください。ホスト名として db ——つまりサービス名——を指定しています。 Docker Compose は同じ compose.yaml に定義されたサービスを同一ネットワーク内に配置し、サービス名で名前解決できるようにします。前回学んだユーザー定義ネットワークの挙動と同じです。Compose を使えばそのネットワーク設定も自動でやってくれます。

基本コマンド

compose.yaml を書いたら、以下のコマンドで操作します。

# バックグラウンドで全サービスを起動する
docker compose up -d

# 起動中のサービスを確認する
docker compose ps

# ログを表示する(-f でリアルタイム追跡)
docker compose logs -f

# 特定サービスのログだけ見る
docker compose logs -f app

# 全サービスを停止・削除する(ボリュームは残る)
docker compose down

# ボリュームごと削除する(DB のデータも消える)
docker compose down -v

# イメージをリビルドして起動する
docker compose up -d --build

up はコンテナの作成・起動、down は停止と削除を行います。down だけではボリュームは削除されないので、データを保持したまま環境を停止できます。開発環境をリセットしたいときは down -v を使います。

コードを修正してアプリをリビルドしたいときは --build を付けて再起動します。ベースイメージを変えていない限りキャッシュが効くので、思ったより速く終わります。

環境変数と .env ファイル

パスワードや API キーを compose.yaml に直書きするのは避けたいところです。Compose は**.env ファイル**を自動で読み込み、${変数名} の形で展開する機能を持っています。

.env ファイルを compose.yaml と同じディレクトリに置きます。

# .env
POSTGRES_PASSWORD=secret
POSTGRES_DB=mydb

compose.yaml 側では変数として参照します。

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}

.env はパスワードなどを含むため、.gitignore に追加してリポジトリに含めないようにしてください。代わりに .env.example(ダミー値を入れたサンプル)をコミットしておくと、新しいメンバーが環境を立ち上げやすくなります。

実践的なコツ

depends_on は起動順を制御するが、「準備完了」は保証しない

depends_on: db と書くと db サービスが起動してから app が起動します。しかし「起動した」と「DB が接続を受け付ける状態になった」は別の話です。PostgreSQL は起動直後に初期化処理を行うため、app が接続しようとした瞬間にまだ準備が終わっていない場合があります。

これに対処するには、app 側で接続を数回リトライするロジックを入れるのが現実的です。あるいは depends_oncondition: service_healthy を使い、ヘルスチェックが通ったことを確認してから起動する方法もあります。

depends_on:
  db:
    condition: service_healthy

ただしこれには db 側に healthcheck の定義が必要です。

docker compose logs -f でデバッグする

コンテナが起動しない・アプリがクラッシュするといったときは、まず docker compose logs -f で全サービスのログを流しましょう。エラーメッセージの多くはログに出ています。

シェルからコンテナ内に入る

動いているコンテナの中に入って調査したいときは exec が使えます。

# app コンテナでシェルを起動する
docker compose exec app bash

# db コンテナで psql を起動する
docker compose exec db psql -U postgres

まとめ

  • Docker Compose は複数コンテナを compose.yaml 1ファイルで定義し、一括管理するツール
  • services / volumes / networks をトップレベルに書き、docker compose up -d で起動する
  • 同じ compose.yaml 内のサービスはサービス名でお互いを名前解決できる
  • .env ファイルで機密情報を外出しにし、compose.yaml にベタ書きしない
  • depends_on は起動順の制御のみ。DB の初期化完了まで待ってくれるわけではない
  • docker compose logs -fexec がデバッグの基本セット

次回はイメージの最適化を扱います。ここまでで動く環境を作れるようになりました。次はそのイメージをより小さく・安全にする方法を学びます。