docker compose downで別プロジェクトを巻き込んで落とした話

Tips

はじめに

業務で複数のプロジェクトを並行して担当しており、それぞれのリポジトリの git ルート直下に deploylocal というディレクトリを作り、その中に環境別の docker-compose.yml を置く構成を採用していました。 ローカル開発では local/docker-compose.yml、検証サーバーへのデプロイでは deploy/docker-compose.yml を使う、という分け方です。

ある日、検証用サーバーで複数プロジェクトのコンテナが同時に動いている状態で、片方のプロジェクトを停止しようと deploy ディレクトリに入って docker compose down を実行したところ、まったく別のプロジェクトのコンテナまで一緒に落ちてしまいました。

原因は Docker Compose の「プロジェクト名(コンテナ名)の解決」にありました。本記事では、この事故がなぜ起きたのかを Compose の仕様レベルで整理し、docker-compose.ymlname プロパティを付けて衝突を防いだ対策をまとめます。

なぜ別プロジェクトまで落ちたのか

Compose のプロジェクト名はディレクトリ名から決まる

Docker Compose は、起動・停止するコンテナやネットワーク、ボリュームを「プロジェクト」という単位でグルーピングします。 そして、このプロジェクト名は 明示的に指定しなければ、Compose ファイルが置かれたディレクトリ名(カレントディレクトリのベース名)から自動的に決まります

筆者の構成では、どのリポジトリも git ルート直下に deploy ディレクトリを作っていました。つまり、

  • ~/projects/project-a/deploy/docker-compose.yml → プロジェクト名 deploy
  • ~/projects/project-b/deploy/docker-compose.yml → プロジェクト名 deploy

というように、異なるリポジトリなのにプロジェクト名がすべて deploy で衝突していた わけです。

down はプロジェクト名を基準にリソースを削除する

ここが事故の核心でした。docker compose down は、カレントディレクトリの Compose ファイルの内容だけを見て削除するわけではありません。 Compose が作成したコンテナやネットワークには、次のようなラベルが付与されています。

# コンテナに付くラベルを確認する
docker inspect project-a-web-1 --format '{{json .Config.Labels}}'
{
  "com.docker.compose.project": "deploy",
  "com.docker.compose.service": "web"
}

com.docker.compose.project の値がプロジェクト名です。down はこのプロジェクト名に紐づくリソースをまとめて停止・削除します。 プロジェクト名が deploy で衝突していたため、project-a の deploy ディレクトリで down を実行した結果、同じ deploy というプロジェクト名でグルーピングされていた project-b 側のリソースまで巻き込んで削除されてしまったのです。

特に共有されてしまうのがデフォルトネットワークです。プロジェクト名が同じだと、どちらも deploy_default という同名のネットワークを使おうとします。down はこのネットワークも削除対象に含めるため、影響範囲が広がりました。

コンテナ名の衝突は up のときにも顔を出す

実は、この問題は停止時だけでなく起動時にも現れます。Compose が生成するコンテナ名は、デフォルトで <プロジェクト名>-<サービス名>-<連番> という形式です。

deploy-web-1
deploy-db-1

プロジェクト名が衝突していると、別リポジトリでも deploy-web-1 のような同じコンテナ名を生成しようとします。 後から up したプロジェクトが先のコンテナを再作成・置き換えてしまうなど、起動順によって挙動が変わる不安定な状態になっていました。今回は「down で巻き込んだ」という形で表面化しましたが、根っこは同じ「プロジェクト名の衝突」です。

検討した対策と採用しなかった案

事故を受けて、いくつかの対策を検討しました。

対策案 内容 採否
ディレクトリ名を変える deployproject-a-deploy のようにリポジトリ固有にする 不採用
-p オプションで毎回指定 docker compose -p project-a down のように都度プロジェクト名を渡す 不採用
name プロパティを付ける Compose ファイルにプロジェクト名を明記する 採用

ディレクトリ名を変える案は、リポジトリ間で deploy / local という統一された構成が崩れてしまうため見送りました。 この構成はチーム間でルールとして定められており、個人の判断で変更はできないためです。 -p オプションで毎回指定する案は確実ですが、「指定し忘れたら同じ事故が再発する」という人為ミスの余地が残ります。コマンドの打ち間違いで本番事故を起こすわけにはいきません。

最終的に、Compose ファイル自体にプロジェクト名を埋め込んでしまえば、どのディレクトリから・誰がコマンドを打っても衝突しない という理由で name プロパティを採用しました。

nameプロパティでプロジェクト名を固定する

Compose Specification では、トップレベルに name プロパティを書くことでプロジェクト名を明示できます。

# project-a/deploy/docker-compose.yml
name: project-a-deploy

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example

もう一方のプロジェクトには別の名前を付けます。

# project-b/deploy/docker-compose.yml
name: project-b-deploy

services:
  web:
    image: nginx:1.27
    ports:
      - "9090:80"

こうしておくと、deploy というディレクトリ名に関係なく、それぞれのプロジェクトが project-a-deploy / project-b-deploy という独立したプロジェクト名で管理されます。 生成されるコンテナ名も project-a-deploy-web-1 のようになり、リポジトリをまたいで衝突しなくなります。

環境ごとに名前を変えておく

筆者は deploylocal で Compose ファイルを分けているので、環境名も name に含めるようにしました。

# local/docker-compose.yml
name: project-a-local

services:
  # ...

これで、同じ project-a の中でも検証用(project-a-deploy)とローカル用(project-a-local)が別プロジェクトとして扱われます。ローカルで down した拍子に検証環境を触ってしまう、といった事故も防げます。

設定が効いているか確認する

name を付けたら、意図したプロジェクト名になっているかを確認します。docker compose config は最終的に解釈される設定を出力してくれるので、ここでプロジェクト名を確かめられます。

# deploy ディレクトリで実行する
docker compose config --format json | grep '"name"'

起動中のプロジェクト一覧も確認しておくと安心です。

docker compose ls
NAME                STATUS              CONFIG FILES
project-a-deploy    running(2)          /home/user/projects/project-a/deploy/docker-compose.yml
project-b-deploy    running(1)          /home/user/projects/project-b/deploy/docker-compose.yml

それぞれ別々のプロジェクト名で並んでいれば、down を実行しても他方を巻き込むことはありません。

ハマったポイント

既存コンテナを一度作り直す必要がある

name を後付けすると、既存のコンテナとは別プロジェクト扱いになります。すでに deploy というプロジェクト名で起動していたコンテナは、name を付けた状態の up では認識されません。 古いプロジェクト名で残っているコンテナを整理してから新しい名前で立ち上げ直す必要があります。

# 古いプロジェクト名のリソースを明示的に停止・削除する
docker compose -p deploy down

# 新しい name で起動し直す
docker compose up -d

このとき、巻き込みたくない他プロジェクトが動いていないかを docker compose ls で必ず確認してから実行しました。対策作業中にまた事故を起こしては本末転倒だからです。

.env の COMPOSE_PROJECT_NAME とどちらが優先されるか

プロジェクト名は name プロパティのほかに、環境変数 COMPOSE_PROJECT_NAME-p オプションでも指定できます。優先順位は次のとおりです。

  1. -p / --project-name コマンドラインオプション(最優先)
  2. 環境変数 COMPOSE_PROJECT_NAME
  3. Compose ファイルの name プロパティ
  4. ディレクトリ名(デフォルト)

name プロパティはあくまで最後の砦で、コマンドラインや環境変数で上書きされる点には注意が必要です。今回は「ファイルに書いておけば誰が打っても安全」という運用を狙ったので、.env 側に COMPOSE_PROJECT_NAME を二重に書かないよう整理しました。

ボリューム名にもプロジェクト名が前置される

名前付きでないボリュームやデフォルトネットワークには、プロジェクト名が接頭辞として付きます。name を変えるとこれらの名前も変わるため、データを保持したいボリュームがある場合は移行を意識する必要があります。 検証サーバーでは保持すべき永続データが少なかったため影響は軽微でしたが、本番環境で同じ対策を入れる際は、ボリュームの引き継ぎを事前に確認しておくべきポイントです。

まとめ

deploylocal のように、複数リポジトリで同じディレクトリ名を使っていると、Docker Compose のプロジェクト名がディレクトリ名から自動決定される仕様により衝突します。 その結果、docker compose down がプロジェクト名を基準にリソースを削除する際、無関係なはずの別プロジェクトまで巻き込んで停止させてしまう、という事故につながりました。

対策として docker-compose.yml のトップレベルに name プロパティを付け、リポジトリ・環境ごとにユニークなプロジェクト名を固定しました。これでディレクトリ名やコマンドの打ち方に依存せず、安定して目的のプロジェクトだけを操作できます。

今回の作業で改めて感じたのは、「デフォルトの暗黙の挙動に運用を委ねていると、構成が増えたときに事故になる」ということです。プロジェクト名のような識別子は、暗黙のデフォルトに任せず明示的に宣言しておくほうが安全だと実感しました。

コメント

タイトルとURLをコピーしました