TsukuLab
Ця стаття доступна 日本語. Версія для Українська готується.
Linux & Docker

Docker Compose 使い方|複数コンテナ管理入門

Оновлено: 2026-03-20 18:32:09佐々木 まい
Docker Compose 使い方|複数コンテナ管理入門

Docker Composeは、ばらばらの docker run を並べる段階から一歩進んで、compose.yaml ひとつで Web と DB をまとめて扱いたい人に向いた道具です。
この記事では、現行推奨の docker compose とCompose Specificationに沿って、最小構成から実務で外しやすい設定までを順番に整理します。
筆者もスマートホームの検証環境を Compose で統一してから、サービス名でそのまま相互接続できる構成に切り替わり、IP を固定しなくて済むようになりました。
その結果、メンバーごとの環境差が目に見えて減りました。
Docker Docsの『Compose file reference』が示す現在の書き方を前提に、古い version 前提の説明に引っぱられず、今そのまま使える形にそろえていきます。
起動順まわりでは depends_on だけを信じて DB の待機に失敗し、アプリが落ちた経験もありますが、healthcheck を入れると再起動後も挙動が安定しました。
『Docker Compose Quickstart』の考え方も踏まえつつ、logs -f で接続タイミングを切り分けるコツ、開発と本番の分離、Secrets の扱いまで、手元で再現できる形でつなげます。
関連記事Linux入門|初心者向けのインストールと基本操作Linux入門。WSL・仮想環境・実機の選び方を比較し、Ubuntu 24.04 LTSの安全な導入手順と初期設定、基本コマンド10選、権限/sudo、トラブル対処まで1本で網羅。Windows利用者のWSL要件やバックアップ必須ポイントも整理します。

Docker Composeとは?Dockerとの違い

Docker Composeは、複数コンテナで構成するアプリケーションを compose.yaml にまとめて定義し、docker compose updocker compose down のような単一コマンドで起動・停止・再作成まで管理するためのツールです。
Docker Docsの『Docker Compose』でも、サービス、ネットワーク、ボリュームを YAML で宣言してライフサイクルを扱う道具として整理されています。
Dockerそのものが「コンテナを動かす基盤」だとすると、Composeはその上で複数のコンテナをひとまとまりのアプリとして扱うレイヤー、と考えると位置づけがつかみやすくなります。
この違いは、Web アプリを例にすると見えやすくなります。docker run だけでもNginxの Web、postgres の DB、redis のキャッシュをそれぞれ立ち上げられますが、実務では 1 コンテナ 1 役割に分けるのが基本です。
アプリと DB を1つのコンテナに詰め込むより、役割ごとに分離したほうが更新範囲を限定でき、ログの切り分けも明快になります。
ところが docker run を複数回打つ運用では、ポート公開、環境変数、ボリューム、ネットワーク参加、コンテナ名の指定がコマンドごとに散らばります。
起動順も人が覚える前提になり、DB が立ち上がる前にアプリを起動して失敗する、といった事故も起きがちです。
筆者もチーム開発で、同じはずの構成なのに docker run のオプションがメンバーごとに少しずつ違い、再現できない環境が増えていったことがあります。
ポート番号だけでなく、マウント先や環境変数の指定漏れまで混ざると、原因調査より「誰のコマンドが正しいのか」を確認する時間のほうが長くなります。
そこで設定を compose.yaml に集約したところ、新しく参加したメンバーもファイルを取得して docker compose up -d を実行するだけで立ち上がるようになり、オンボーディングは数分で済む流れに変わりました。
コマンドの記憶ではなく、構成そのものをファイルとして共有できる点がComposeの強みです。

Dockerとの違いは「単体実行」か「構成管理」か

Dockerの docker run は、1つのコンテナをその場で起動するには向いています。
検証用に nginx を1個だけ立ち上げる、短時間の動作確認をする、といった場面では素早く扱えます。
一方でComposeは、複数サービスの関係を含めて定義しておき、同じ構成を何度でも再現するための仕組みです。
単体コンテナの起動コマンドを置き換えるものというより、複数コンテナ運用で発生する「設定の散在」を抑えるための道具だと捉えると混乱しません。
ネットワークやボリュームの管理も差が出る部分です。docker run では、どのネットワークに参加させるか、どの永続化領域を使うかを都度指定します。
Composeではプロジェクト単位でネットワークが作られ、サービス名で名前解決できるため、アプリ側は dbredis といったサービス名で接続先を表現できます。
前のセクションで触れた通り、これが IP 固定に頼らない構成につながります。

現行仕様はCompose Specificationが前提

ファイル形式については、古い記事の version: "3"docker-compose.yml という書き方に引っぱられやすいところです。
現行の正式仕様は『Compose file reference』が示すCompose Specificationで、かつて分かれていた 2.x 系と 3.x 系の書式はこの仕様に統合されています。
そのため、今の compose.yaml ではトップレベルの version は必須ではなく、省略して問題ありません。
過去の書式を見かけても「古い情報」と切り分けて読むと整理しやすくなります。
CLI も同じで、現在の推奨はハイフンなしの docker compose です。
以前広く使われていた docker-compose は旧来の独立バイナリで、読み替え対象として知っておくとログや既存手順書を読むときに役立ちます。
新しく触る環境では、まず docker compose を基準に覚えておけば十分です。
{{ogp:https://docs.docker.com/reference/compose-file/|Compose file reference|Find the latest recommended version of the Docker Compose file format for defining multi-container applications.|}}

v1、v2、v5の位置づけ

系譜を短く整理すると、Composeは 2014 年に公開された v1 から始まり、その後はCompose Specificationに沿う v2 系へ移りました。
2025 年に公開された v5 は公式 Go SDK の導入が主な違いで、利用者から見た機能面の位置づけは v2 相当です。
つまり、いま学ぶときに意識したいのは「v2 のコマンド体系と Compose Specification に基づくファイル形式」で、v1 や旧 2.x / 3.x は歴史的な経緯として押さえれば足ります。

Composeが向く範囲と、そこから先の選択肢

Composeがもっとも力を発揮するのは、単一ホスト上で Web、DB、キャッシュ、ジョブ実行用コンテナなどをまとめて動かす場面です。
開発環境、検証環境、CI、小〜中規模の運用では、1つの compose.yaml を中心に構成を共有できる恩恵が大きく、学習コストと得られる再現性のバランスも取りやすい部類に入ります。
一方で、複数ホストへの配置、オートスケール、自己修復、宣言的な大規模運用まで求める段階では、Kubernetesのようなコンテナオーケストレーション基盤が候補になります。
Composeはあくまで単一ホスト中心の複数サービス管理に強い道具であり、クラスタ全体の自動化や大規模スケールを前提にした設計とは守備範囲が異なります。
つまりDockerがコンテナ実行の土台、Composeが単一ホストの複数コンテナ管理、Kubernetesなどがその先の大規模運用という並びで捉えると、使い分けの境界が見えます。
つまり、まとめるとDockerはコンテナ実行の基盤、Compose は単一ホストで複数コンテナをまとめる管理ツール、そしてKubernetesは複数ホストの大規模運用に向いたオーケストレーション基盤です。
用途ごとに道具を使い分けると整理しやすくなります。

まず動かす:Web + DB の最小 compose.yaml

Step 1: プロジェクトディレクトリを作る

まずは、空の作業用ディレクトリを1つ作ります。
Compose はそのディレクトリをひとまとまりのプロジェクトとして扱うので、後から docker compose psdocker compose down を打ったときも対象がぶれません。
プロジェクト名はデフォルトでディレクトリ名になるため、名前を見ただけで用途がわかるものにしておくと管理が楽になります。

mkdir compose-web-db
cd compose-web-db

この段階では、compose.yaml だけ置けば十分です。
最初からソースコードを bind mount(ホスト側のディレクトリをそのままコンテナへ見せる方法)でつなぐ構成にすると、権限やパスの違いで止まりやすくなります。
筆者は最初の確認では、まず『nginx』とPostgreSQLの既成イメージだけで立ち上げて、Compose そのものが動く感触を先につかむ組み方をよく使います。
このほうが、どこでつまずいたのかを切り分けやすくなります。
{{ogp:https://hub.docker.com/_/nginx|nginx - Official Image | Docker Hub||}}

Step 2: 最小の compose.yaml を書く

このファイルを、そのまま compose.yaml という名前で保存してください。
現行の書き方では version: は不要です。
古い記事にある docker-compose.ymlversion: "3.x" は過去の書式です。
今は 『Compose file reference』 の Compose Specification ベースで読むのが基準になります。
services: web: image: nginx ports:

  • "8080:80" db: image: postgres environment: POSTGRES_USER: appuser # primary POSTGRES_PASSWORD: example POSTGRES_DB: appdb ports:
  • "5432:5432" volumes:

named volume をコンテナ内の /var/lib/postgresql/data にマウント

  • postgres_data: volumes: postgres_data: ここで押さえたい項目は4つです。services は「何のコンテナを動かすか」を書く場所で、この例では webdb の2サービスがあります。ports はホスト側のポート公開で、"8080:80" は「ホストの 8080 へ来たアクセスを、コンテナの 80 へ流す」という意味です。『nginx』はコンテナ内で 80 番ポートを使うので、ブラウザでは http://localhost:8080 へアクセスします。

NOTE

まだ学習段階なら、DB の 5432:5432 は「ホストPCから直接つなげて中身を確認するための公開」と考えると整理しやすくなります。
コンテナ同士の通信だけなら、公開ポートがなくても db:5432 で到達できます。

Step 3: 起動・確認・終了の基本コマンド

ファイルを書いたら、まずは起動します。-d は detached mode(バックグラウンド実行)です。
端末を占有しないので、その後の確認コマンドをすぐ打てます。

docker compose up -d

初回はイメージ取得が入るため少し待ちます。
『nginx』はイメージがまだない状態だと取得に時間がかかりますが、ローカルにそろった後の再起動はぐっと短くなります。
『postgres』はさらに初回起動でデータディレクトリの初期化が走るので、2回目以降より待ち時間が長く見えます。
ここで焦って再実行すると、起動中なのか失敗したのか見分けがつきにくくなるので、次の確認を挟むと流れが安定します。
起動状態は ps で確認できます。

docker compose ps

webdb が表示され、STATUS が Up になっていれば起動できています。webPorts0.0.0.0:8080->80/tcp のような表示があれば、ホストの 8080 で受けてコンテナの 80 へ転送している状態です。
ブラウザで http://localhost:8080 を開くと、『nginx』の初期ページが表示されます。
docker compose logs -f

`-f` は follow で、ログを継続的に表示します。`web` と `db` をまとめて眺めると、どちらが先に立ち上がって、どのタイミングで接続しようとしているかが見えます。
`-f` は follow で、ログを流し続けます。`web` と `db` をまとめて眺めると、どちらが先に立ち上がって、どのタイミングで接続しようとしているかが見えます。筆者も Web 側が DB へつなぎに行く構成では、この画面を開いたままにすることが多いです。接続失敗の瞬間と DB 側の起動完了ログが前後に並ぶので、設定ミスなのか、単に初期化待ちなのかの判断が速くなります。
作業を止めるときは次のコマンドです。
```bash
docker compose down

これでコンテナとネットワークは片づきますが、今回の postgres_data ボリュームは残るので、DB データは保持されます。
学習中はこの挙動のほうが扱いやすく、再起動しても初期化をやり直さずに済みます。
反対に、DB をまっさらな状態へ戻したい場面では、ボリュームも削除するオプション付きの down を使う、という切り分けになります。
ここではまず「down ではデータは消えない」と覚えておくと、再起動後に挙動が変わらない理由が見えやすくなります。

compose.yaml の基本要素をやさしく解説

まずは、全体像が見える最小の compose.yaml を置いておきます。細かい意味はこのあと順番にほどいていけば大丈夫です。

services:
 web:
 image: nginx
 ports:
 - "8080:80"
 db:
 image: postgres
 environment:
 POSTGRES_USER: appuser # replica
 POSTGRES_PASSWORD: example
 POSTGRES_DB: appdb
 volumes:
 - postgres_data:
volumes:
 postgres_data:

この形なら、Web サーバーとして『nginx』、DB として『postgres』をまとめて起動できます。
サンプル内での名称(POSTGRES_PASSWORD やボリューム名)を通しで統一しておくと、読者がコピーして実行するときの混乱を避けられます。
この形なら、Web サーバーとして『nginx』、DB として『postgres』をまとめて起動できます。
ファイルを書いたら、まずはバックグラウンドで立ち上げます。

docker compose up -d

状態確認は次のコマンドです。

docker compose ps

起動ログを追いかけるときは、こちらを使います。

docker compose logs -f

Docker Docsの『Docker Compose Quickstart』でも、先に完成形を動かしてから要素ごとに理解していく流れが採られています。
Compose は項目名が多く見えますが、最初に見るべきなのは servicesportsvolumesenvironment あたりです。

services / image / build

services は、アプリを構成するコンテナ群を論理名付きで並べる場所です。
上の例なら webdb がサービス名で、この名前がそのまま Compose 内の識別子になります。
コンテナ名を毎回意識するより、「Web 役」「DB 役」と役割で読めるので、複数サービスでも頭の中を整理しやすくなります。
その各サービスの中で、どのイメージを使うかを書くのが image です。
たとえば image: nginxDocker Hubの公式『nginx』イメージを使う指定で、image: postgres は公式『postgres』イメージを使う指定です。
既成のイメージをそのまま使いたいときは、まず image を選ぶと考えると迷いません。
一方の build は、手元の Dockerfile から自分でイメージを作る指定です。
たとえばアプリ本体を自作していて、依存パッケージや実行コマンドも自分で定義したいなら build: . のように書きます。
違いをひとことで言うと、image は「できあがったものを取ってくる」、build は「手元の設計図から作る」です。

services:
 web:
 image: nginx
 app:
 build: .

学習の最初は、Web サーバーや DB のように完成済みの公式イメージを image で使い、アプリ部分だけ build にする構成がいちばん腹落ちしやすいです。

ports と内部通信の関係

ports は、ホストからコンテナへ入る入口を作る設定です。
書式は "host:container" で、"8080:80" ならホストの 8080 番をコンテナの 80 番へ転送します。
『nginx』はコンテナ内で 80 番を待ち受けるので、ブラウザでは http://localhost:8080 にアクセスします。

services:
 web:
 image: nginx
 ports:
 - "8080:80"

ここで混同しやすいのが、ホスト公開とコンテナ同士の通信は別だという点です。
Compose で同じプロジェクトとして起動したサービスは、内部ネットワーク上でサービス名で名前解決されます。
つまり、アプリから『postgres』へつなぐときに必要なのは db:5432 であって、5432:5432 の公開ではありません。
たとえば app から db へ接続するなら、接続先ホストは localhost ではなく db です。localhost はそのコンテナ自身を指すので、複数コンテナ構成ではここを取り違えた瞬間に接続できなくなります。
筆者も Compose を使い始めたころは、Web 側の設定で DB_HOST=localhost を書いてしまい、ログを見てやっと気づいたことがありました。docker compose logs -f でアプリ側の接続失敗と DB 側の起動ログを並べて見ると、この手の勘違いは見抜きやすくなります。
ポート公開は、ブラウザで見たい、ホスト PC の DB クライアントから直接つなぎたい、といった場面だけに付けると構成がすっきりします。
内部通信だけなら公開は不要です。

volumes(named / bind)と top-level 定義

volumes は、コンテナを作り直しても残したいデータを保管するための設定です。
DB のデータ保存先をコンテナの中だけに置くと、コンテナ再作成で消えてしまいます。
そこで『postgres』なら /var/lib/postgresql/data にボリュームをマウントします。

services:
 db:
 image: postgres
 volumes:
```yaml
services:
 db:
 image: postgres
 volumes:
 - postgres_data:
volumes:
 postgres_data:

この postgres_data のようなものが named volume です。
Compose 管理の保存領域として扱われ、コンテナとは別に残ります。
この pgdata のようなものが named volume です。
Compose 管理の保存領域として扱われ、コンテナとは別に残ります。
筆者は named volume の名前を明示しておく書き方をよく使います。
無名のままでも動きますが、あとで docker volume ls などを見たときに、どのデータがどこにあるか追いやすく、検証環境を壊さずに在処をつかめます。
もうひとつよく使うのが bind mount です。
こちらはホスト側のディレクトリをそのままコンテナへ見せる方式で、開発中のソースコード反映に向いています。

services:
 app:
 build: .
 volumes:
 - ./src:

named volume は「アプリが作るデータを残す箱」、bind mount は「手元のファイルをそのまま見せる窓口」と考えると整理しやすくなります。
前者は DB データやアップロードファイル向け、後者は開発中のコードや設定ファイル向けです。
top-level の volumes: は、プロジェクト全体で使う named volume の定義場所です。
サービス内の volumes: と名前が同じでも役割は別で、サービス側は「どこへ付けるか」、top-level 側は「その保存領域を用意するか」を表しています。
{{ogp:https://hub.docker.com/_/postgres|postgres - Official Image | Docker Hub||}}

environment と .env の補間

environment は、コンテナへ環境変数を渡す設定です。
『postgres』公式イメージでは POSTGRES_USERPOSTGRES_PASSWORDPOSTGRES_DB をここで指定できます。
すでに見た最小例のように、キーと値の形で書くのがいちばん読みやすいです。

services:
 db:
 image: postgres
 environment:
 POSTGRES_USER: appuser # analytics
 POSTGRES_PASSWORD: secret
 POSTGRES_DB: appdb

配列形式でも書けます。

services:
 db:
 image: postgres
 environment:
 - POSTGRES_USER=appuser
 - POSTGRES_PASSWORD=secret
 - POSTGRES_DB=appdb

どちらも動きますが、値の見落としが少ないので、筆者はマッピング形式を選ぶことが多いです。
Compose では .env ファイルを使った補間もできます。
たとえば compose.yaml 側で次のように書きます。

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

同じディレクトリに置いた .env はこうです。

POSTGRES_USER=appuser
POSTGRES_PASSWORD=secret
POSTGRES_DB=appdb

これで値をファイルの外に出せます。
設定値を差し替えたいときに compose.yaml 本体を何度も編集しなくて済むので、学習用・検証用・ちょっとした開発用で切り替える場面でも見通しが保てます。
Compose の正式な仕様は『Compose file reference』にまとまっていて、.env 補間もここで整理されています。

networks の基本

Compose では、特別な指定をしなくても デフォルトネットワーク が自動で作られます。docker compose up -d を実行すると、そのプロジェクト専用のネットワークに各サービスが参加し、webdb のようなサービス名で通信できます。
初心者の段階では、この自動作成されるネットワークだけで十分なことがほとんどです。
カスタムネットワークを使いたいときは、top-level に networks: を定義して、サービスごとに参加先を書きます。

services:
 web:
 image: nginx
 networks:
 - frontend
 db:
 image: postgres
 networks:
 - backend
networks:
 frontend:
 backend:

この構成だと webfrontenddbbackend に所属するので、そのままでは相互通信しません。
役割ごとに通信範囲を分けたいときに使う考え方です。
まずは「Compose はネットワークを自動で作る」「同じネットワークの中ではサービス名で届く」の2点が押さえられていれば十分です。

depends_on と healthcheck の正しい関係

depends_on は、あるサービスを別のサービスより先に起動したいときの宣言です。たとえばアプリが DB に依存するなら、次のように書けます。

services:
 app:
 build: .
 depends_on:
 - db
 db:
 image: postgres

ただし、ここで保証されるのは 起動順 までです。db コンテナのプロセスが開始されたことは示せても、「DB が接続を受け付ける準備まで終わった」ことまでは保証しません。
この違いを知らないと、depends_on を書いたのにアプリが起動直後に接続失敗する、という状態にぶつかります。
そこで組み合わせたいのが healthcheck です。
これは、そのコンテナが外から見て正常に応答できるかを定期的に確認する仕組みです。
Docker Compose Quickstartの Redis 例では、interval5stimeout3sretries5start_period10s という値が使われています。
意味は次の通りです。interval は何秒ごとに検査するか、timeout は1回の検査を何秒待つか、retries は何回失敗したら不健康とみなすか、start_period は起動直後の猶予時間です。
Redis なら、たとえば次のように書けます。

services:
 redis:
 image: redis
 healthcheck:
 test: ["CMD", "redis-cli", "ping"]
 interval: 5s
 timeout: 3s
 retries: 5
 start_period: 10s

この考え方は『postgres』やアプリ側にも応用できます。
つまり、depends_on は順番の整理、healthcheck は準備完了の判定です。
Compose を読みやすく保つコツは、この2つを別物として理解することにあります。

TIP

docker compose ps では、サービスが起動しているだけでなく、healthcheck を設定していれば healthy かどうかも追えます。
起動したのに接続できない場面では、まずここを見ると切り分けが速くなります。
関連記事ラズパイ 自宅サーバー構築|Samba/Nextcloud/VPNRaspberry Pi 5で自宅サーバーを始めるなら、まずはSambaでLAN内の共有フォルダを作り、外出先からはWireGuard経由で入る構成にすると、最短で実用品になります。

複数コンテナがつながる仕組み

デフォルトネットワークと名前解決

Docker Composeで複数コンテナを起動すると、まず押さえたいのがプロジェクト単位のネットワークです。
Docker Docsの『Docker Compose | Docker Docs』でも整理されている通り、Compose はサービス群をひとまとまりのアプリケーションとして扱い、その単位ごとにネットワークを管理します。docker compose up -d を実行すると、特別な指定がなくてもデフォルトネットワークが自動作成され、同じプロジェクトのサービスはそこへ参加します。
このとき便利なのが、サービス名で相手を見つけられることです。
たとえば webdb というサービスを定義していれば、web コンテナから db:5432 のように指定してPostgreSQLへ接続できます。
IP アドレスを調べて設定ファイルへ書く必要はありません。
Compose が同一ネットワーク内で DNS 解決を行うため、db という名前がそのまま接続先として使えます。
筆者が最初にこの仕組みの恩恵を強く感じたのは、検証環境を別のマシンへ持っていったときでした。
以前は IP を固定したり hosts を配ったりしていたのですが、Compose に寄せてからは compose.yaml をそのまま持っていけばサービス名でつながります。
IP 固定や hosts 管理が不要になり、環境をコピーして起動するだけで同じ接続関係を再現できるので、検証用の構成を置き換える心理的な負担がぐっと減りました。
前のセクションで触れた networks: の明示定義は、通信範囲を分けたいときに使います。
逆に言えば、学習用の Web と DB の構成なら、まずは自動作成されるデフォルトネットワークの挙動を理解するだけで十分です。
Compose の複数コンテナ構成が「思ったより簡単」に見えるのは、この自動ネットワークとサービス名解決が土台にあるからです。
{{ogp:https://docs.docker.com/compose/|Docker Compose|Learn how to use Docker Compose to define and run multi-container applications with this detailed introduction to the to|}}

ホスト公開ポートとコンテナ間通信の違い

ここで混同しやすいのが、ports: で設定するホスト公開ポートと、コンテナ同士の内部通信の違いです。
両者は似ているようで役割がまったく別です。 ports: は、ホストのポートをコンテナのポートへ結びつける設定です。
たとえば『nginx』がコンテナ内で 80 番ポートを待ち受けているなら、8080:80 と書くことでブラウザから localhost:8080 にアクセスできるようになります。
これは外部、つまりホスト OS やブラウザ、別マシンからそのコンテナへ入るための入口です。
一方で、同じ Compose プロジェクト内のコンテナ同士が話すときは、ポート公開は不要です。app から db へ接続するなら db:5432app からRedisへ接続するなら redis:6379 という形で到達できます。
ここで使うのはホスト側の 54326379 ではなく、相手コンテナが内部で待ち受けているポートです。
Compose のネットワーク上で直接やり取りするので、ホスト公開は経由しません。
この違いを理解すると、ports: を必要なサービスだけに絞れるようになります。
たとえばブラウザで表示確認したい web は公開し、アプリからしか使わない dbredis は公開しない、という構成です。
そうすると compose.yaml を見たときに「どのサービスが外に顔を出していて、どれが内部専用か」が読み取りやすくなります。
ネットワーク設計を難しく考える前に、まず外向けの入口なのか、内部通信なのかで分けて考えると整理しやすくなります。

NOTE

ports: がなくても、同じネットワーク上のサービスからは サービス名:ポート で到達できます。web から db へ接続できないときに 5432:5432 を足したくなりますが、まず確認すべきは公開設定ではなく、接続先名と参加ネットワークです。

プロジェクト名と命名規則

Compose を使っていると、ネットワーク名やボリューム名の先頭に見慣れない接頭辞が付くことがあります。
これは Compose がプロジェクト名を基準に各リソースへ名前を付けているためです。
たとえばディレクトリ名が myapp なら、デフォルトネットワークが myapp_default のような名前で作られる、というイメージです。
ボリュームやコンテナ名にも同じ考え方が反映されます。
この命名規則のおかげで、同じホスト上で複数の Compose 構成を動かしても衝突しにくくなります。web というサービス名を別プロジェクトでも使えているのは、内部ではプロジェクト名を含めた単位で整理されているからです。
開発用、検証用、デモ用と似た構成を並べたいときに、この仕組みが効いてきます。
プロジェクト名はデフォルトでは作業ディレクトリ名から決まりますが、固定したい場合は -p オプションや COMPOSE_PROJECT_NAME で変更できます。
チームでディレクトリ名が人によって違うと、生成されるネットワーク名やボリューム名も変わります。
そこでプロジェクト名を明示すると、docker compose psdocker network ls を見たときの並びがそろい、どのリソースがどの構成に属しているか追いやすくなります。
Compose の複数コンテナ構成は、単に YAML でまとめて起動できるだけではありません。同じプロジェクト名のもとで、ネットワーク、名前解決、ボリューム名まで一貫して束ねるところに価値があります。
サービス名で通信できる理由も、別環境へ持っていっても構成が崩れにくい理由も、この命名ルールまで含めて見ると腑に落ちます。

よく使うコマンドと開発フロー

起動・再作成・停止

日常的な流れは、まず build、次に up、動いたら pslogs、中を見たいときに exec、止めるときに down、不要なコンテナだけ消したいときに rm、という順で覚えると迷いません。
Docker公式ドキュメントでも、Compose は複数サービスのライフサイクルをまとめて扱う道具として整理されています。
単発の docker run を積み重ねるより、作成・起動・確認・停止の流れをひとつの文脈で扱えるのが利点です)。 docker compose build は、アプリ用の Dockerfile を変えたときや、依存パッケージをイメージに入れ直したいときに使います。
逆に、image: nginximage: postgres のように既成イメージをそのまま使っていて、compose.yaml の設定だけを変えたなら、毎回 build は要りません。
その場合は docker compose up -d で十分です。up は定義されたサービスのコンテナを作成して起動し、定義やイメージに変更があれば対象を再作成して反映します。
たとえば web の公開ポートや環境変数を直しただけなら、docker compose up -d で変更が反映されます。
アプリのベースイメージを更新した、RUN apk add ... を書き換えた、といったイメージ自体の作り直しが必要な変更では、docker compose build のあとに docker compose up -d を重ねる流れになります。
筆者は「設定変更か、イメージ変更か」を先に切り分けてからコマンドを打つようにしていて、ここが整理できると無駄な再作成が減ります。
停止は docker compose down が基本です。
これは Compose プロジェクトのコンテナやネットワークを停止・削除するコマンドで、開発をいったん閉じるときの区切りに向いています。
ただし、通常の down ではボリュームは残るので、PostgreSQLのデータを保持したままコンテナだけ作り直す運用ができます。
前のセクションまでで見た通り、Compose はネットワークや名前付けもまとめて管理しているので、down で一度閉じても、再び up -d すれば同じ接続関係に戻せます。

TIP

compose.yaml の記述だけを直したときは docker compose up -dDockerfile や依存物を変えたときは docker compose build のあとに docker compose up -d、DB の中身は残したいときは down だけでボリュームは消さない、という切り分けにすると手順が安定します。

状態確認とデバッグ

起動したあとに最初に見るコマンドは docker compose ps です。
どのサービスが起動中か、終了していないか、どのポートが公開されているかを一覧で確認できます。up -d はバックグラウンド実行なので、コマンドが返ってきただけではアプリが正しく立ち上がったとは限りません。ps を見れば、web が起動しているのか、db が起動直後に落ちたのかを切り分けられます。
その次に使うのが docker compose logs です。
全体をざっと見るなら docker compose logs、起動中の出力を追い続けるなら docker compose logs -f を使います。
特定サービスだけを見たいときは docker compose logs -f web のようにサービス名を付けます。web が 502 を返しているのか、db が接続待ちなのか、redis が初期化で止まっているのかを個別に追えるので、原因の切り分けが速くなります。
筆者は複数サービス構成の検証では、logs -f を一本流し見することが多いです。web のリクエスト直後に API のエラーが出て、その少し後に db の接続失敗と redis のタイムアウトが並ぶ、といった流れが時系列で見えるので、API・DB・キャッシュの相互作用で起きる不具合を再現するときに強い手がかりになります。
個別ログを順番に開くより、まず全体の流れを眺めてから深掘りするほうが、詰まっている層を特定しやすくなります。
コンテナの中に入って確認したい場面では docker compose exec を使います。
たとえば web コンテナにシェルで入るなら docker compose exec web shbash が入っているイメージなら docker compose exec web bash です。
Compose の exec はサービス名で対象を選べるので、コンテナ ID を調べる手間がありません。
入ったあとに env で環境変数を見る、設定ファイルを cat する、アプリの実行コマンドを手で試す、といった確認ができます。
この exec は「起動はしているが中身が想定通りか分からない」ときに役立ちます。ps は外から見た状態、logs は標準出力、exec はコンテナ内部の実態確認という役割分担です。

クリーンアップと削除

開発を続けていると、停止しただけのコンテナや、作り直しの途中で残ったリソースを整理したくなる場面があります。
そこで使うのが docker compose rm です。
これは停止済みのサービスコンテナを削除するコマンドで、down のようにプロジェクト全体を一度に片づけるというより、不要になったコンテナを整理するための道具です。
コンテナだけ消して、ボリュームは残す、という運用とも相性があります。 docker compose downdocker compose rm の違いは、前者が「いま動いている Compose 構成をいったん閉じる」操作で、後者が「停止済みコンテナの実体を掃除する」操作だと捉えると整理できます。
普段の終了は down、作り直しを重ねて停止済みのコンテナが残ったときの整理は rm という使い分けです。
永続データを残したいケースでは、コンテナだけ消してボリュームには触れない流れが向いています。
PostgreSQL公式イメージは /var/lib/postgresql/data に既存データがあれば初期化をスキップするので、ボリュームを保持して up -d し直せば、DB を毎回作り直さずに済みます。
初回だけ実行される初期化 SQL や /docker-entrypoint-initdb.d の処理も、既存データを使う限り再実行されません。
アプリ側だけ再ビルドしたいのに DB まで初期化してしまう、という遠回りを避けられます。
一方で、設定を根本からやり直したいときは、コンテナを止めて削除し、必要ならボリュームも含めて再作成する、という判断になります。
ここで「どこまで消すか」を曖昧にすると、残しておきたかったデータまで失うか、逆に古い状態を引きずって原因が見えなくなるかのどちらかに寄りがちです。
筆者は、アプリの不具合調査ではまずコンテナ再作成、DB 初期化が必要な検証だけボリュームも含めて切り離す、という順番で進めています。
Compose は再現性のための道具でもあるので、毎回フルリセットするのではなく、どの層を捨ててどの層を残すかを意識すると開発フローが安定します。

つまずきやすい点とトラブルシューティング

ポート競合の見分け方

docker compose up -d が失敗したとき、初心者が最初に見落としやすいのがポート競合です。
たとえばNginxはコンテナ内で 80 番ポートを使い、PostgreSQLは 5432、Redisは 6379 を使います。
Compose 側で ports:8080:805432:5432 を書いていても、ホスト側の 8080 や 5432 がすでに別プロセスに使われていると、コンテナ作成そのものが止まります。
このときはアプリの不具合ではなく、ホスト OS 側の待ち受けがぶつかっています。
エラーメッセージに bind 失敗や address already in use と出ていたら、まず疑うべきはここです。
Linux なら ss -ltnp で待ち受け中の TCP ポートを見られます。
古い環境では netstat -ltnp でも追えます。
5432 を使っているのがローカルのPostgreSQLなのか、8080 を占有しているのが別の開発サーバーなのかが分かれば、対処は整理できます。
回避策は単純で、ホスト側ポートをずらすことです。
たとえば 8080:80 が埋まっているなら 8081:80 に変える、ローカルに DB を入れていて 5432 が埋まっているなら Compose 側を別ポートに寄せる、という考え方です。
コンテナ内ポートまで変える必要はなく、まずは左側のホストポートを調整します。
開発用なら 127.0.0.1:8080:80 のようにバインド先 IP も明示しておくと、意図しない外部公開も防げます。
もうひとつ見逃されがちなのが、ディレクトリ名を変えたあとにプロジェクト名も変わり、以前の Compose プロジェクトが別名で残っているケースです。
同じ構成を「別プロジェクト」として起動してしまい、古い方がすでにポートを掴んでいる、という流れは珍しくありません。docker compose ps だけでなく docker ps まで見て、似た名前のコンテナが残っていないか確認すると切り分けが進みます。
Compose の仕様やプロジェクト単位の扱いはDocker Compose | Docker DocsCompose の仕様やプロジェクト単位の扱いはDocker Compose | Docker Docsの説明と一致します)。

depends_on と readiness の誤解を正す

depends_on を書いたのにアプリが DB 接続で落ちる、というのも定番のつまずきです。
原因は、depends_on が「起動順」をある程度そろえてくれても、「接続できる状態になるまで待つ」わけではないからです。
コンテナプロセスが立ち上がった直後は、DB やキャッシュがまだ初期化中ということが普通にあります。
この誤解を解くには、healthcheckcondition: service_healthy を組み合わせます。
Docker Compose公式のQuickstartでも Redis を例にした healthcheck が紹介されています。
ここでは interval: 5stimeout: 3sretries: 5start_period: 10s という設定が使われています。
実運用でもこの考え方はそのまま効きます)。

services:
 redis:
 image: redis
 healthcheck:
 test: ["CMD", "redis-cli", "ping"]
 interval: 5s
 timeout: 3s
 retries: 5
 start_period: 10s
 web:
 build: .
 depends_on:
 redis:
 condition: service_healthy

この形にしておくと、webredis コンテナが「起動した」だけでは進まず、healthcheck が通るまで待てます。
筆者も depends_on だけで済ませていた頃は、初回起動でだけアプリが接続失敗し、その次はなぜか通る、という再現しにくい崩れ方に悩まされました。healthcheck を入れてからは初回起動失敗が目に見えて減り、CI の安定度も上がりました。
ログを見ると、アプリの問題に見えていたものの多くが、実際には依存サービスの準備待ち不足だったと分かります。
なお、Compose ファイルの現行仕様は version: を前提に覚えるより、Compose Specification ベースで読むほうが混乱が少なくなります。
Docker公式のCompose file referenceでもその前提で整理されています。
{{ogp:https://docs.docker.com/compose/gettingstarted/|Quickstart|Follow this hands-on tutorial to learn how to use Docker Compose from defining application dependencies to experimenting|}}

ボリュームと権限の落とし穴

「コンテナを作り直したらデータが消えた」という現象は、実際には消えたのではなく、永続化先を定義していなかっただけ、ということがよくあります。
PostgreSQLならデータは /var/lib/postgresql/data、Redisなら /data をボリュームに逃がしておかないと、コンテナ再作成時に中身も一緒に入れ替わります。
Compose ではサービス側の volumes: にマウントを書くだけでなく、名前付きボリュームを使うならトップレベルの volumes: も定義しておく必要があります。
ここが抜けていると、どこに何が残るのかが見えにくくなります。
たとえば DB のパスワードを変えたのに反映されない、初期化 SQL が再実行されない、という相談も、既存ボリュームが残っていて初回初期化が終わっているケースが大半です。
PostgreSQL公式イメージはデータディレクトリが空のときだけ初期化を走らせるので、環境変数 POSTGRES_PASSWORD/docker-entrypoint-initdb.d の内容は「初回だけ効く」と捉えると整理できます。
逆に、毎回まっさらなつもりで検証していたのに古いデータが残っていた、という混乱も起きます。
権限まわりも厄介です。
ホストのディレクトリをバインドマウントしたとき、コンテナ内プロセスの UID とホスト側の所有者が噛み合わず、Permission denied になることがあります。
特にログ出力先や DB のデータディレクトリで起きると、アプリ本体より先に保存処理で落ちます。
Compose の設定をいくら見直しても直らないときは、マウント先の所有者とパーミッションに目を向けると原因が見つかります。docker compose exec でコンテナに入り、実際にどのユーザーで動いているか、対象ディレクトリに書き込めるかを確認すると話が早くなります。
プロジェクト名の変化もボリューム増殖の原因です。
Compose はディレクトリ名をもとにプロジェクト名を決めることがあるため、フォルダ名を変えると、同じ compose.yaml でも別プロジェクト扱いになります。
その結果、ネットワークやボリュームが新しい名前で追加され、「前のデータが消えた」と見えることがあります。
実際には別名のボリュームが増えただけ、ということが多いです。

YAML 文法エラーのチェックリスト

Compose で起きるエラーのうち、内容のわりに時間を奪うのが YAML の文法ミスです。
設定の意味を考える前に、まず構文として読めているかを疑うほうが近道になる場面があります。
YAML は自由度が高いぶん、インデントひとつで別物になります。
詰まりやすい点はだいたい決まっています。

  • スペース数がそろっておらず、services: の下の階層が崩れている
  • コロン : の前後を崩してしまい、キーと値の区切りとして解釈されない
  • タブ文字が混ざっていて、見た目はそろっていても YAML として壊れている
  • リストの - の位置がずれて、ports:volumes: の配列として読まれない
  • volumes をサービス内とトップレベルで混同している
  • 文字列として扱うべき値を不用意に裸で書き、意図しない解釈になる 特にインデントは、エラー文が直接「何行目の何文字目が悪い」と親切に教えてくれないこともあり、設定の意味の話に見えて実は字下げの問題、ということがよくあります。エディタでタブを可視化すると、見た目では気づけなかった混入が見つかります。ports:environment: のように配列とマップが混ざる場所は、1 段深くなっているかを重点的に見ると切り分けやすくなります。

docker compose と docker-compose の違い

コマンド名の違いも、初学者が混乱しやすいポイントです。
現在の推奨は docker compose で、docker-compose は古い書き方として残っている環境があります。
歴史をたどると Compose v1 は 2014 年に公開され、その後の系譜を経て現在は Docker CLI に統合された形が中心です。
Docker公式のHistory and development of Docker Compose解説でも、v1 から現行実装までの流れが整理されています。
実際に手元の環境でどちらが使えるかを見るなら、docker compose version を打つのが早いです。
これでバージョンが返ってくれば、いま使うべき入り口は docker compose です。
一方で古い記事や古い CI 設定には docker-compose up -d が残っていることがあり、コピーペーストしたら「そのコマンドはない」となることがあります。
内容の違いというより、CLI の入口が別物になっていると捉えると混乱しません。
この違いは Compose ファイルの書き方にも波及します。
昔の解説では version: "3" のような記述が前提で語られている一方、現行では Compose Specification ベースの読み方が中心です。
古い docker-compose 前提の記事と、新しい docker compose 前提の記事を混ぜて読むと、コマンド名だけでなく記法の前提までずれて見えるので、そこが引っかかりになります。
フォルダ名変更に伴うプロジェクト名の変化も、この文脈で一緒に覚えておくと整理しやすくなります。docker compose で同じファイルを起動しているつもりでも、ディレクトリ名が変わればネットワーク名やボリューム名の接頭辞も変わります。
結果として、古いリソースが残ったまま新しいものが増え、「ネットワークが増殖した」「同じ DB が二重にあるように見える」といった状態になります。
コマンドの新旧だけでなく、プロジェクト名の決まり方まで把握しておくと、Compose の挙動が一気につながります。
{{ogp:https://docs.docker.com/compose/intro/history/|History and development|Explore the evolution of Docker Compose from v1 to v5, including CLI changes, YAML versioning, and the Compose Specifica|}}

環境変数・Secrets・.env の使い分け

環境変数と .env の基本

Docker Composeで設定値を切り分けるとき、まず押さえたいのが「環境変数」と「.env ファイル」の役割の違いです。
環境変数は、コンテナの中でアプリに渡す設定値そのものです。
一方の .env は、compose.yaml の中で ${VAR_NAME} のように書いた値を補間するための入力元として使うのが基本です。
ここを混同すると、「.env に書いたから安全」「environment: に展開された値も同じ扱い」と考えてしまい、設定管理が曖昧になります。
たとえば compose.yamlPOSTGRES_USER: ${POSTGRES_USER} のように書いておくと、Compose は .env やシェルの環境変数から値を読み取って置き換えます。
Docker Compose公式のQuickstartでも、この補間の流れが最小構成の中です。
開発では「ポート番号」「デバッグフラグ」「接続先ホスト名」など、機密ではない設定を .env に集めておくと、compose.yaml 自体は読みやすいまま保てます)。
ただし、.env機密情報の保管庫ではありません
リポジトリにそのまま置かれやすく、レビュー時の差分にも出やすく、手元の作業でコピーされる機会も多いからです。
筆者は .env.example にキー名だけ並べ、実際の .env は各自のローカルで作る形をよく使います。
この運用なら、必要な変数名を共有しつつ、パスワードやトークンそのものは Git に載せずに済みます。

Secrets

DB パスワードや API トークンのような値は、.envenvironment: に直書きするより、Composeの Secrets に寄せたほうが整理しやすくなります。
『Secrets in Compose』 にある通り、Secrets は「書けば使える」仕組みではなく、トップレベルで定義してから、各サービスで参照する二段階で設定します。 compose.yaml では、まずトップレベルの secrets:db_password のような名前を定義します。
そのうえで、services: 配下の app:db:secrets: を書き、「このサービスはその secret を使う」と明示します。
この二段階になっているおかげで、どの機密をどのサービスに渡しているかが設定ファイル上で追いやすくなります。
Secrets として渡された内容は、コンテナ内では通常 /run/secrets/<name> にファイルとして見えます。
たとえば db_password なら /run/secrets/db_password です。
ここが環境変数と違うところで、機密がそのまま printenv や設定ダンプに出てこない構成を取りやすくなります。
環境変数へ機密を直書きしない理由はここにあります。
環境変数は、アプリの例外ログ、デバッグ出力、プロセス情報の確認、設定一覧の表示といった場面で思わぬ形で露出しやすく、値の追跡範囲が広がりがちです。
筆者も本番系の構成で、以前は DB パスワードを環境変数で渡していましたが、Secrets に移してからは誤って設定内容をログへ吐いてしまう事故の不安が減りました。
アプリの起動ログに設定オブジェクトを丸ごと出す実装は珍しくなく、その中にパスワードが混ざると後始末が面倒です。
ファイル経由に寄せるだけで、そうした露出面を一段減らせます。
ここで区別しておきたいのが、ビルド時 secretsランタイム secrets です。
前者はイメージを build するときに一時的に使う認証情報で、後者はコンテナを起動してからアプリに渡す機密です。
本稿で扱っているのは後者、つまり Compose でサービスへ渡すランタイム中心の話です。
Dockerfile の build 中に private registry や private package repository へ認証する話とは、考える場所が別です。
{{ogp:https://docs.docker.com/compose/how-tos/use-secrets/|Secrets in Compose|Learn how to securely manage runtime and build-time secrets in Docker Compose.|}}

_FILE 変数と安全な読み出し

Secrets を使うときに実務でよく出てくるのが、_FILE 形式の環境変数です。
これは「値そのもの」ではなく「値が入ったファイルのパス」を渡すやり方で、公式イメージでも採用例があります。
たとえばPostgreSQL公式イメージでは、通常の POSTGRES_PASSWORD だけでなく、ファイル経由の指定パターンを使える設計が知られています。
Secrets が /run/secrets/... に置かれるなら、POSTGRES_PASSWORD_FILE=/run/secrets/db_password のように組み合わせるわけです。
この形の利点は、既存アプリや公式イメージが環境変数ベースの設定を前提にしていても、機密の実体はファイル側に置いたまま渡せることです。
見た目は環境変数を使っていても、中身はパス文字列だけなので、値そのものを直接持ち回らずに済みます。
もし利用中のアプリに _FILE 形式の仕組みがなければ、アプリ側で /run/secrets/<name> を読みに行く実装にする方法もあります。
たとえば起動時にファイルを読み込み、その内容を DB 接続設定へ入れる構成です。
Node.js、Python、Go のどれでも珍しくないパターンで、エントリポイントスクリプトで cat /run/secrets/db_password した結果をアプリ設定に流し込む方法もあります。
Secrets の扱いをアプリの初期化時に閉じ込めると、設定の責務が分かれます。

WARNING

PASSWORDPASSWORD_FILE の両方を同時に置けるアプリでは、どちらを優先するかが実装で決まっています。
機密をファイル運用へ寄せるなら、片方だけにそろえると読み違いが起きません。

.dockerignore と機密混入の防止

.env を Git に入れない運用でも、そこで安心し切れない場面があります。
見落とされやすいのが ビルドコンテキスト です。docker compose build や build を伴う up では、カレントディレクトリ配下のファイルが Docker デーモンへ送られます。.dockerignore を用意していないと、.env までコンテキストに含まれ、DockerfileCOPY . . のような記述次第ではイメージ内へ入ってしまいます。
この混入は、Compose のランタイム secrets をきれいに分けていても、ビルド段階で台無しになります。
開発用の .env にトークンや接続文字列を書いていた場合、イメージレイヤに残り、後から取り除きにくい状態になります。
Docker Compose公式のQuickstartでも .dockerignore の重要性に触れていますが、実務では .gitignore だけでなく .dockerignore も同じ温度感で管理する必要があります)。
最低限、.env.git、ローカルの秘密鍵、テスト用ダンプ、エディタ設定のうち不要なものは .dockerignore に入れておくと、ビルド対象を意図した範囲に絞れます。
機密情報の扱いは「保存場所」だけでなく、「どの経路でコンテナやイメージに入るか」まで見ないと整理しきれません。
Compose の Secrets、.env の補間、.dockerignore による除外は、それぞれ別の層の話ですが、まとめて設計すると事故が起きる場所を減らせます。

開発・CI・本番での分け方

複数ファイルの重ね合わせと優先度

開発・CI・本番を 1 つの compose.yaml だけで回そうとすると、どこかで無理が出ます。
開発ではソースを bind mount したいのに、本番ではイメージに同梱したアプリをそのまま動かしたい、といった差分が典型です。
そういうときはベースとなる compose.yaml を共通部分に寄せ、環境ごとの差分だけを追加ファイルへ切り出します。
基本形は次の通りです。

docker compose -f compose.yaml -f production.yml up -d

-f は指定した順にマージされ、後ろのファイルが前の設定を上書きします。
つまり compose.yaml を土台にして、production.yml の内容で差分を被せる構成です。
開発用なら compose.yaml 単体、CI 用なら compose.yamlci.yml、本番なら compose.yamlproduction.yml という分け方にすると、どの環境で何が変わるのかを YAML の差分として追えます。
起動後の基本操作も、環境が分かれていても同じです。up は作成と起動、down は停止とネットワーク類の片付け、ps は稼働状況の確認、logs は標準出力の確認、exec は起動中コンテナ内でのコマンド実行を担当します。build はイメージの再構築、rm は停止済みコンテナの削除を担当します。
たとえば本番反映後に挙動を追うなら docker compose -f compose.yaml -f production.yml ps で状態を見て、特定サービスだけ追いたいなら docker compose -f compose.yaml -f production.yml logs -f web のようにサービス名を付けます。
複数サービスがある構成では、全体ログより webworker を絞ったほうが原因に早く届きます。
コンテナに入って確認したい場面では exec を使います。
Web アプリにシェルで入るなら、たとえば次の形です。

docker compose -f compose.yaml -f production.yml exec web sh

イメージによっては bash が入っていないので、まずは sh を試すほうが手堅いです。
アプリの設定ファイルや環境変数、生成物の有無をその場で確認できるため、ログだけでは切り分けにくい不具合で役立ちます。

production.yml の書き方例

本番向けの override では、開発特有の bind mount を外すのが定番です。
開発ではホスト側のソースコードを即時反映したいので mount しますが、本番ではビルド済みイメージの中にアプリを閉じ込め、コンテナの中身を固定したほうが扱いやすくなります。
ベースの compose.yaml を開発寄りに書くと、たとえば次のようになります。
services: web: build: . ports:

  • "8080:80" volumes:
  • .: command: ["nginx", "-g", "daemon off;"] db: image: postgres environment: POSTGRES_PASSWORD: example
これに対して `production.yml` で bind mount を消し、イメージ指定へ寄せます。
```yaml
services:
 web:
 image: myapp:web-prod
 volumes: []
 restart: always

この形なら、開発では .: が効いて手元の変更を反映し、本番では myapp:web-prod の中に入っているアプリをそのまま起動できます。
実務ではこの切り替えを入れてから、「本番だけホスト側のファイルに引っ張られて挙動が変わる」事故を避けやすくなりました。
とくに Dockerfile できちんと成果物を作る流れができると、build の役割も明確になります。
開発中は docker compose build web でアプリ側のイメージを更新し、不要になった停止済みコンテナは docker compose rm で整理する、という流れです。
なお、公式のDockerドキュメントでも Compose の複数ファイルは後勝ちで重ねる前提です。
環境差分を「別の YAML に書く」発想は、手順メモではなく設定として残せる点が強みです。

プロジェクト名の設計

Compose を環境ごとに安定して運用するなら、プロジェクト名を固定しておくと見通しが一気によくなります。
プロジェクト名はコンテナ名、ネットワーク名、ボリューム名の接頭辞に使われるため、ここが毎回ぶれると docker psdocker network ls を見たときに判別しづらくなります。
一時的に指定するなら -p を使います。

docker compose -p myapp-dev up -d
docker compose -p myapp-ci ps
docker compose -p myapp-prod logs -f web

固定値として運用へ組み込むなら、COMPOSE_PROJECT_NAME を使う方法もあります。
開発端末では myapp-dev、CI では myapp-ci、本番では myapp-prod のように分けると、同じホストに複数環境があっても衝突を避けやすくなります。ps の表示でもどの環境の web なのかが接頭辞で判別できるので、停止やログ確認の誤操作を減らせます。
この設計は downrm の挙動にも効きます。
たとえば開発環境を落としたいのに本番側まで巻き込む、という事故は、プロジェクト名が曖昧な運用で起こりがちです。
名前を固定しておけば、docker compose -p myapp-dev down の対象がはっきりします。
ライセンス面を軽く触れると、個人利用のDockerは無料です。
チームで管理機能を使う場合はDocker公式サイトの料金表で Team が年払いで月額15ドル/ユーザー、月払いで月額16ドル/ユーザーです。
Compose 自体の操作を覚える段階では、この点が障壁になりにくいのも入りやすい理由のひとつです。

CI での軽量起動とテスト

CI では「本番に近いものを全部起動する」より、「テストに必要な最小サービスだけを起動する」ほうが回しやすくなります。
たとえば Web アプリのテストで dbredis だけあれば足りるなら、監視や管理画面、開発補助ツールまで一緒に上げる必要はありません。
Compose なら対象サービスを絞って起動できます。

docker compose -f compose.yaml -f ci.yml logs db
docker compose -f compose.yaml -f ci.yml exec -T db psql -U postgres -c '\l'

ここでの up は必要な依存を立ち上げる入り口、ps はジョブ途中の死活確認、logs は失敗時の原因追跡、exec は中でテスト補助コマンドを打つ用途です。
CI では対話操作が不要なので、exec-T を付けて TTY を切る場面もよくあります。
テスト後に停止済みコンテナが残る構成なら、rm で掃除しておくと次のジョブでノイズが減ります。
筆者は CI で全部入り構成をそのまま起動していた時期に、アプリ本体と無関係な補助サービスの起動待ちでジョブが長引き、失敗箇所の切り分けにも手間取りました。
最小構成へ分けてからは、落ちたときに見るべきログが絞られます。
Redis を使うテストでは、公式の Quickstart にある healthcheck の interval: 5stimeout: 3sretries: 5start_period: 10s のような待ち方を下敷きにしておくと、起動直後の不安定なタイミングをまたいで判定できます。

部分更新(--no-deps)の使いどころ

運用で効くのが、一部サービスだけを差し替える場面です。
たとえば web のアプリコードだけ更新したいのに、毎回 dbredis まで巻き込んで再作成すると、余計な停止が増えます。
そういう場面では --no-deps を使うと、依存サービスを触らず対象だけ更新できます。

docker compose -f compose.yaml -f production.yml up -d --no-deps web

この流れでは、まず buildweb イメージを作り直し、その後 up -d --no-deps webweb だけを再作成します。
更新後は logs -f web で起動ログを追い、必要なら exec web sh で中に入って生成物や設定を確認します。
もしロールバックや整理で停止済みコンテナが残ったら、rm で片付ける形です。
筆者の感覚では、本番更新で --no-deps を使うと DB やキャッシュを動かしたまま web だけ入れ替えられるので、停止範囲を小さく保てます。
とくにPostgreSQLやRedisを含む構成では、アプリ側だけの修正なのに依存先まで巻き込む理由がありません。
無停止を保ちやすく、実際のダウンタイムを短く収めやすい手順です。
反対に、スキーマ変更や接続先設定の変更を含む更新では、--no-deps で切り抜けるより構成全体として整合を見たほうがよい場面もあります。
その判断基準が曖昧なうちは、「アプリだけの差し替えか、依存サービスの変更を含むか」で分けると迷いません。
Compose は docker run を並べる運用よりも、こうした更新単位の判断をコマンドに落とし込みやすいところが実務で効いてきます。

Docker Composeの次の一歩

Compose が向く規模/向かない規模

Docker Composeが最も力を発揮するのは、単一ホストで複数サービスをまとめて扱う場面です。
たとえば webdb、そこに redis を足した構成を compose.yaml に集約して、起動・停止・ログ確認をひとまとまりで回す使い方は、開発環境や CI、小〜中規模の運用と相性が合います。docker run を並べていた頃の「どのコンテナにどのポートを開けたか」「どの順で立ち上げるか」がファイルに残るので、あとから見返しても構成の意図を追えます。
筆者も最初は Raspberry Pi 1台のホームラボで Compose を使っていました。
Home Assistantまわりの補助サービスを同じ定義にまとめると、再起動や設定変更のたびに手順を思い出す必要がなく、1台で完結する環境にはちょうどよかったです。
逆に、台数が増えて「どれかのノードが落ちたら別のノードで自動復旧したい」「負荷に応じて複数台へ広げたい」という要件が出てくると、Compose だけでは担当範囲を超えます。
その境目は、コンテナ数の多さよりも複数ホストをまたいだ運用をどこまで自動化したいかで考えると整理できます。
単一ホスト上で「何を起動するか」を宣言するのが Compose の守備範囲で、複数ホストにまたがって「どこで動かすか」「落ちたらどう戻すか」「どう増減させるか」まで面倒を見る段階では、別の道具が必要になります。

Kubernetes との境界

その次の候補として名前が挙がるのが、Kubernetesのようなオーケストレーション基盤です。
Compose はアプリを束ねて扱うための道具、Kubernetes は複数ノード上でスケジューリングや自己修復、スケール制御まで含めて扱うための基盤、という切り分けで捉えると迷いません。
学習コストは一段上がりますが、運用の要求が上がったときに必要になるのは YAML の書き方そのものより、クラスタ全体をどう設計するかという視点です。
Docker Compose自体も止まっているわけではありません。
Compose は 2014年に v1 が公開され、その後は Compose Specification ベースへ整理され、2025年には v5 が公開されました。
v5 は利用者目線の機能が v2 から急に増えた版というより、Go SDK の導入で実装基盤を整えた流れとして見ると理解しやすいです。
つまり、Compose はいまも現役ですが、守備範囲はあくまで Compose のままです。
だからこそ、単一ホストの構成管理に集中したいときは強力で、クラスタ運用まで一気に背負わせないほうが構成判断を誤りません。
Compose から Kubernetes へ進むべきか迷ったら、「同じ compose.yaml を複数台に配っている」「障害復旧を手作業で回している」「サービスの増減をホストごとに調整している」といった兆候が出ていないかを見ると判断しやすくなります。
そこまでは Compose で十分に戦えますし、そこを超えたら Kubernetes などを検討する、という順番で問題ありません。

次にやるべきアクションリスト

ここから先は、知識を増やすより手元の構成を1段ずつ実運用寄りに寄せるほうが身につきます。
最初の一歩としては、Docker 公式の Quickstart をそのまま再現し、サービス間通信と healthcheck の流れを自分の端末で確認するのが近道です。
文章で読んで理解したつもりでも、up して logs を見て、exec で中に入るところまで触ると、Compose の役割が立体的に見えてきます。
その次は、手元の小さなアプリを web 単体の構成から web + db + redis に置き換えてみてください。
NginxPostgreSQLRedisのような定番の公式イメージを使うと、ネットワーク名解決、永続化、起動順の考え方が一度に学べます。
特に postgres の初期化や redis の役割が入ると、「単にコンテナを動かす」から「複数サービスを構成として扱う」へ視点が切り替わります。
開発と運用の差を埋める練習としては、compose.yaml を土台に devproduction の2段構成へ分けるのも効果的です。
開発では bind mount やデバッグ用設定を載せ、本番向けでは不要な公開ポートや補助ツールを外す、という整理を一度やると、ファイル分割の意味が腹落ちします。
前のセクションで触れた複数ファイルのマージは、この段階で初めて実感を伴って理解できます。
認証情報の扱いも次の宿題です。
最初は .env で始めても構いませんが、そのまま固定化せず、段階的に Secrets へ寄せていくほうが後で困りません。
Compose を学ぶゴールは YAML を書けることではなく、構成・設定・運用手順を分離して、変更に強い状態を作ることにあります。
着手順を絞るなら、この4つで十分です。

  1. 公式 Quickstart を再現して、起動からログ確認まで一通り触る 2. 自分のアプリを web + db + redis の3サービス構成に置き換える 3. compose.yaml と上書きファイルで devproduction を分ける 4. .env に置いた機密情報を Secrets ベースへ移していく Compose を学んだあとに見るべき景色は、「もっと複雑な YAML を書くこと」ではありません。単一ホストで気持ちよく回る構成をまず固め、その先で複数ホスト運用の必要が見えたらKubernetesのような次の基盤へ進む。その順番なら、道具に振り回されず、要件に合わせて選べます。 関連記事Docker入門|インストールと初コンテナ起動Dockerは、アプリをコンテナという軽い実行環境で動かすための仕組みです。イメージとコンテナの違い、Docker EngineとDocker Desktopの役割さえ最初に押さえれば、Windows(WSL2)・Mac・Linuxのどれでも導入の道筋はすぐ見えてきます。

Поділитися статтею

佐々木 まい

IT企業でのシステムエンジニア経験を経て、スマートホーム導入のコンサルティングに転身。Home AssistantやESPHomeを使った自宅オートメーションを日々研究中。