TECHNOLOGY

Docker Composeの導入:複数コンテナの構成管理と起動の自動化(Docker入門)

本記事では、Docker Composeを用いた複数コンテナの構成管理と、システム起動の自動化について解説します。単体のコンテナを動かす段階から先に進み、Webサーバー、データベース、管理ツールといった複数のサービスが連携するシステムを、一つの設定ファイル(docker-compose.yml)で制御するための方法を示します。また、セキュリティ対策(.env)やコンテナ同士の連携を保証するヘルスチェック(自動待機)などについても取り扱います。

1つ前の記事

一つ前の記事はこちらです。名前付きボリュームによるデータ永続化とユーザー定義ネットワークについて解説しています。

目次

はじめに

Dockerは、イメージからコンテナを作成し、コンテナを起動してサービスを提供した後、停止、削除するのが一連の流れになります。この際、実際のプロジェクトは単一のプログラムで完結することは稀で、Webサーバー、アプリケーション、データベース、キャッシュなど、役割の異なる複数のコンテナが連携して一つのシステムを構成するのが一般的です。

これらを個別に管理しようとすると、運営コストが大きくなってしまいます。具体的には、ネットワーク設定、ボリューム割り当て、環境変数など、数十行に及ぶオプションをコンテナごとに打ち込む必要があり、人的エラーが発生しやすくなります。また、どの順番で、どの設定で起動すべきかを、誰かの記憶やドキュメントに頼ることになり、チーム内での環境を完全に一致させることが困難になります。

Docker Composeは、これらの設定を一つの設計図(docker-compose.yml)に集約し、コマンド一発でシステム全体を制御するためのツールです。

Docker Composeの導入と設定ファイルの概要

Docker Composeは、コンテナやボリューム、ネットワークなどに関する設定をYAML(YAML Ain’t Markup Language)形式のテキストファイルとして定義し、まとめて管理するためのツールです。設定された内容に基づいて、コンテナを一括実行したり、一括停止、削除したりできます。

単体のコンテナだけを動かすのであれば、docker runコマンドで十分ですが、複数ある場合に個別にコマンドを叩く手間を解消するのがDocker Composeの役割です。

Docker ComposeはDocker Engineとは別のソフトウェアであるため、インストールが必要です。ただし、Docker Desktopを使用する場合には、既に入っているため、別途インストールする必要はありません。それ以外の場合のインストール方法はこちら(公式ページ)に記載されています。

Docker Composeでは、docker runなどのコマンドのオプションとして渡していた内容をdocker-compose.ymlファイルに書き込みます。

例えば、docker runのコマンド

docker run --name apache_server -d -p 8000:80 -v C:\Test\my-site\:/usr/local/apache2/htdocs/ httpd 

を、Compose の設定ファイルの形(YAML形式)で書くと、以下のようになります。意味については後述するため、こんな感じに書くものだと受け取ってください。

services:              # サービス群(コンテナ実行)の宣言
   web_server:         # サービス名をweb_serverという名前に指定(論理名)
      image: httpd     # イメージとしてhttpdを使用
      ports:           # ポートの定義(複数あり得るのでリスト形式)
         - 8000:80 
      volumes:         # マウントの定義(複数あり得るのでリスト形式)
         - C:\Test\my-site\:/usr/local/apache2/htdocs/
      container_name: apache_server       # コンテナの物理名

このように構造化することで、「どの設定が、どのサービスに属しているか」をひと目でわかるようになり、多人数の開発でも設定の意図が正確に伝わるようになります。

設定ファイルのフォーマット

設定ファイル(docker-compose.yml)のフォーマットはYAML形式で、字下げによって情報の親子関係を表現します。ファイル名はオプション(-f)により変更することができますが、デフォルトではdocker-compose.ymlを使用します。

docker-compose.ymlの大項目(YAMLの一番上の階層)として、よく用いるのは次の3つです。

項目必須/任意意味
services必須システムを構成する「実行プログラム(コンテナ)」を定義します。
networks任意コンテナ同士が通信するための「仮想ネットワーク」を定義します。
volumes任意データを保存するための「永続ストレージ」を定義します。

これらの大項目の直下には、論理名を記述します。

services:
   論理名:
      ~(各設定値) 
volumes:
   論理名:
      ~(各設定値) 
networks:
   論理名:
      ~(各設定値) 

この論理名はDocker Compose内で使われる名前で、Dockerエンジンが直接管理する物理的な名前とは異なります。

名前の種類説明
論理名
(YAML上の名前)
Docker Compose内部での名前です。

servicesに書いた論理名(サービス名)は同じネットワーク内のコンテナ同士が「通信相手」を特定するための名前(ホスト名)として使われます。

volumesnetworkdsの直下に書く名前は、サービス(services)が「どのネットワークに参加するか」「どのボリュームを使うか」を指定する際に参照するラベルです。
物理名
(実際の名前)
Docker Engine上の物理名であり、重複することができません。サービス名はdocker ps、ボリューム名はdocker volume ls、ネットワーク名は docker network lsで表示される名前です。

書き込める設定値は大項目(services, volumes, networksなど)ごとに異なります。

コンテナの定義(services)

servicesに書き込める代表的な項目は以下になります。まず、大項目の下には論理名を記述します。

Composeの項目対応するコマンド/オプション説明
[論理名]コマンド:docker run
オプション:--name [名前]

設定ファイル:
services:
 db-server: # これが論理名(サービス名)


サービスを識別するためのIDです。この名前がそのまま、同じネットワーク内のコンテナ同士が通信するための「ホスト名(ドメイン名)」になります。例えば、db-serverと名付ければ、他のコンテナからはping db-serverで通信できます。

そして、論理名の下には以下の設定値を指定することができます。

Composeの項目対応するコマンド/オプション説明
buildコマンド:docker build
オプション:-t [イメージ名]:[タグ] [ビルドコンテキスト]

設定ファイル:
build: [ビルドコンテキスト]
(例: build: .
ビルドコンテキストにあるDockerfileを基に、独自のDockerイメージをビルドします。通常、docker buildコマンドを実行する際は-tオプションで名前を指定しますが、Compose経由で実行すると、プロジェクト名に基づいた名前が自動的に付与されます。
imageコマンド:docker run
オプション:-d [イメージ名]:[タグ]

設定ファイル:
image: [イメージ名]:[タグ]
(例:image: postgres:18.3-alpine
Docker Hubなどのレジストリから取得するベースイメージを指定します。buildは自分で作るのに対して、imageは既存の完成品を使います。latest(最新版)ではなく、タグにより特定のバージョンを明示することで、時間が経っても同じ挙動の再現性を担保できます。
portsコマンド:docker run
オプション:-p [ホスト側のポート]:[コンテナ側のポート]

設定ファイル:
ports:
- [ホスト側のポート]:[コンテナ側のポート]
(例:- 8080:80
コンテナ内部で動いているサービスを、自分のPC(ホスト)からアクセスできるように「窓口」をつなげる設定です。この設定により、例えばブラウザからlocalhost:8080にアクセスすると、コンテナ内のWebサーバー(80番ポート)へ通信が転送されるようになります。
volumesコマンド:docker run
オプション:-v [ホスト側のパス]:[コンテナ側のパス]

設定ファイル:
volumes:
- [ホスト側のパス]:[コンテナ側のパス]
(例: - ./src:/app
ホスト側のファイルやディレクトリをコンテナ内にマウント(共有)します。開発環境においては、自分のPCでコードを書き換えた瞬間にコンテナ内にも反映されるため、必須の設定です。一方で、データベースなどのデータを永続化を目的にする場合は名前付きボリュームがよく使われます。
environmentコマンド:docker run
オプション:-e [変数名]=[値]

設定ファイル:
environment:
- [変数名]=[値]
(例:- DB_PASSWORD=pasword123
データベースのパスワードや接続先ホスト名など、プログラムに渡す環境変数を定義します。Node.jsであればprocess.env、Pythonであればos.environを通じて読み込まれる値です。
depends_onコマンド:(該当するコマンドなし)
オプション:(なし)

設定ファイル:
depends_on:
- [サービス名]
(例:- db
「DBコンテナが起動してから、Webアプリコンテナを起動する」といった、コンテナ間の起動順序を制御します。depends_onのリストにあるコンテナをすべて起動した後に、設定した本人が起動します。
container_nameコマンド:docker run
オプション:--name [名前]

設定ファイル:
container_name: [名前]
Docker Engine上での物理的なコンテナ名を指定します。しかし、物理名を固定してしまうと、コンテナを2台に増やす際に名前が重複してエラーになるという副作用があります。また、プロジェクト間の衝突回避のため、デフォルトではプロジェクト名が接頭辞として追加されますが、名前を固定するとプロジェクト間で衝突する可能性があるため、原則書きません。
commandコマンド:docker run
オプション: [イメージ] [コマンド]

設定ファイル:
command: [実行したいコマンド]
(例:command: npm run dev
Dockerfile内で定義されているデフォルトの起動コマンド(CMD)を上書きします。開発時のみデバッグモードで起動したい場合や、ホットリロードを有効にしたコマンドで実行したい場合に使用します。ホットリロードは、プログラムのソースコードを書き換えた際、アプリを停止・再起動することなく、即座に変更内容を実行中のアプリに反映させる機能のことです。
networksコマンド:docker run
オプション:--network [ネットワーク名]

設定ファイル:
networks:
- [ネットワークの論理名]
(例:- backend-net
コンテナを指定した仮想ネットワークに参加させます。Docker Composeでは、同一ファイル内のサービス同士はデフォルトで通信可能ですが、明示的にネットワークを分けることで、DBを外部ネットワークから隔離するなど、セキュリティの制御が可能になります。
deploy: resources: limits:コマンド:docker run
オプション:--cpus=[値], --memory=[値]

設定ファイル:
deploy:
 resources:
  limits:
   cpus: [値]
   memory: [値]
(例:cpus: '0.5', memory: 512M
コンテナが使用できるCPUやメモリの上限を制限します。特定のコンテナが負荷高騰やバグで暴走した際に、ホストPC全体の資源を使い果たしてシステム全体がフリーズ(ハングアップ)してしまうのを防ぐための安全装置です。

cpusは1つのコアの100%を1.0と数えます。たとえば、2.0とすれば、2コア文をフルに使ってもよいという意味になります。

ボリュームの定義(volumes)

volumesに書き込める代表的な項目は以下になります。まず、大項目の下には論理名を記述します。

Composeの項目対応するコマンド/オプション説明
[論理名]コマンド:docker volume create
オプション:[プロジェクト名]_[論理名]

設定ファイル:
volumes:
 db_data: # これが論理名
Docker Compose内(YAML内)でボリュームを識別するためのラベル(論理名)を定義します。名前を書くことで、ComposeはOS上に実体となるボリュームを自動的に作成します。name指定がない場合、これが「プロジェクト名_論理名」としてOSに登録されます。プロジェクト名が接頭辞として追加されるため、プロジェクト間の衝突が起こらないようになっています。

そして、論理名の下には以下の設定値を指定することができます。

Composeの項目対応するコマンド/オプション説明
nameコマンド:docker volume create
オプション:[物理名]

設定ファイル:
name: [物理名]
(例:name: "production_db_backup"
OS上で管理される名前(物理名)です。docker volume lsで表示される名前です。これを書くと、上記の論理名による自動命名は無視されます。コンテナ名(container_name)と同様、基本的には自動命名に任せるのが安全です。外部で既に作成済みのボリュームを、Composeプロジェクトから名前指定で繋ぎたい場合のみ利用します。
driverコマンド:docker volume create
オプション:--driver [値]

設定ファイル:
driver: [ドライバー名]
(例:driver: local
ボリュームのデータを「どこに、どのような形で」保存するかを制御するエンジンを指定します。

通常のローカル開発ではlocal(自分のPC)を使用しますが、クラウド環境などへの保存先の切り替えも可能です。設定値をクラウド用のドライバーに変更するだけで、アプリケーションのコードを変更することなく、保存先をネットワークストレージに移行できます。
driver_optsコマンド:docker volume create
オプション:--opt [キー]=[値]

設定ファイル:
driver_opts:
  [キー1] : [値1]
  [キー2] : [値2]
ボリュームドライバーに対して、OSレベルの細かなマウントオプションを直接渡すために使用します。例えば、書き込み権限の制限や、NFS(ネットワーク経由のストレージ)といった特殊なファイルシステムの指定が可能です。

ローカル開発で重宝するのは、localドライバーを使いながら実体は特定のディレクトリを指すボリュームを作りたい場合です。これにより、Dockerにボリュームとして管理させつつ、データの保存先を任意の絶対パスに固定できるため、データのバックアップや他ツールとの連携がスムーズになります。
externalコマンド:(該当するコマンドなし)
オプション:(なし)

設定ファイル:
external: true
通常、Docker Composeはupコマンドを実行した際、ファイル内に定義されたボリュームやネットワークを自動的に新規作成しようとします。しかし、external: trueを指定すると、自分では作らず、既存のOS上に存在するリソースを探して利用するという挙動に変わります。docker-compose.ymlファイル(別プロジェクト)間で同じデータベースを共有したい場合や、Composeの管理サイクル(downによる一括削除など)から特定のデータを完全に切り離して保護したい場合に必須となる設定です。
labelsコマンド:docker volume create
オプション:--label [キー]=[値]

設定ファイル:
labels:
 [キー]: [値]
ボリュームに「タグ」のような管理用メタデータを付与します。「どのプロジェクトのボリュームか」をラベルで識別し、一括削除などの自動化処理に役立てます。具体的にはdocker volme ls --filter label=...のように特定の条件でリソースを絞りこみます。

ネットワークの定義(networks)

networksに書き込める代表的な項目は以下になります。まず、大項目の下には論理名を記述します。

Composeの項目対応するコマンド/オプション説明
[論理名]コマンド:docker network create
オプション:[プロジェクト名]_[論理名]

設定ファイル:
networks:
 frontend-net: # これが論理名
YAML内でサービス(services)がどのネットワークに参加するかを指定するための識別名です。name指定がない場合、これが「プロジェクト名_論理名」という物理名でOSに登録されます。プロジェクト名が接頭辞として追加されるため、プロジェクト間の衝突が起こらないようになっています。

そして、論理名の下には以下の設定値を指定することができます。

Composeの項目対応するコマンド/オプション説明
nameコマンド:docker network create
オプション:[物理名]

設定ファイル:
name: [物理名]
(例:name: "external-api-net"
OS上の物理名です。docker network lsで表示させたい名前を直接指定します。これを書くと、上記の論理名による自動命名は無視されます。
driverコマンド:docker network create
オプション:--driver [値]

設定ファイル:
driver: [値]
(例:driver: bridge
ネットワークの通信方式(ドライバ)を指定します。ローカル開発では、一つのホスト(自分のPC)内でコンテナをつなぐbridgeがデフォルトであり、最も一般的です。他にも、ホストと同じネットワーク設定を共有するhostや、複数のサーバーにまたがるコンテナ間通信を実現するoverlayなどがあります。
driver_optsコマンド:docker network create
オプション:--opt [キー]=[値]

設定ファイル:
driver_opts:
 [キー]: [値]
ネットワークドライバに対して、OSレベルのネットワークインターフェイスの設定などの詳細なパラメータを渡します。最もよく使われるのは、bridgeドライバ使用時にホストOS側で作成される仮想ブリッジの名前を固定する設定です。通常、Dockerはbr-xxxxとしったランダムな名前を割り振りますが、これをbr-webなどに固定することで、ホスト側のファイアウォール設定や監視ツールとの連携が容易になります。その他、通信の最大サイズを規定する調整などに使います。
externalコマンド:(該当するコマンドなし)
オプション:(なし)

設定ファイル:
external: true
通常、Docker Composeはupを実行した際、ファイル内に定義されたネットワークを自動的に新規作成します。しかし、external: trueを指定すると自分では作成せず、既にOS上に存在するネットワークを探して利用するという挙動に変わります。主に、異なるdocker-compose.ymlファイル(別プロジェクト)間で通信を実施したい場合に使用します。共通の外部ネットワークをexternalとして参照することで、互いのコンテナが通信できるようになります。また、Composeの管理サイクル(downによる一括削除など)からネットワークを切り離して維持したい場合にも必須です。
internalコマンド:docker network create
オプション:--internal

設定ファイル:
internal: true
この項目をtrueに設定すると、そのネットワークに属するコンテナを隔離状態にします。具体的には、そのネットワーク経由での外部(インターネット等)への接続を完全に遮断し、同じネットワークに属するコンテナ間だけの通信に限定します。データベースや認証サーバーなど、外部と直接通信する必要がなく、かつ外部から直接アクセスされてはいけないコンテナを保護する際に使用します。
ipamコマンド:docker network create
オプション:--subnet [CIDR], --gateway [IP], --ip-range [CIDR]

ipam:
 config:
  - subnet: [CIDR]
   gateway: [IPアドレス]
   ip_range: [CIDR]
(例:
- subnet: "192.168.1.0/24"
 gateway: "192.168.1.1"
 ip_range: "192.168.1.128/25"
ネットワークのIPアドレス管理を定義します。通常、Dockerは開いているプライベートIP帯を自動的に割り当てますが、ipamを使うことで、サブネット(利用可能なIPの範囲)やデフォルトゲートウェイを明示的に固定できます。

subnet:ネットワーク全体の範囲を指定します。
gateway:デフォルトゲートウェイのIPを指定します。
ip_range:指定したサブネットの中から実際にコンテナに割り当てるIPの範囲をさらに限定します。

また、ip_rangeを使うことで、例えば、前半はIPの手動割り当て(固定IP用)に残しておき、後半のIPだけをDockerの自動割り当てに使うといった、ネットワーク設計が可能になります。開発環境のIP帯が社内ネットワークと競合する際の回避策として有効です。
labelsコマンド:docker network create
オプション:--label [キー]=[値]

設定ファイル:
labels:
 [キー]: [値]
ネットワーク管理用のメタデータを付与します。ボリュームと同様、プロジェクトの識別や自動化処理に役立てます。docker network ls --filter label=...のように特定の条件でリソースを絞りこみます。

設定ファイルの作成例

例として、PostgreSQLのデータベースとデータベース管理マネージャーを起動するための設定ファイル(docker-compose.yml)を示します。名前付きボリューム(my-db-data)を定義することでデータベースの永続化を行い、データベースと管理マネージャーを同じネットワーク(db-net)に接続することで、サービス間の通信を可能にしています。構築の詳細は、前回の記事で解説した通りですが、今回はそれらをDocker Composeの形式に落とし込んでいます。打ち込んだ数々のオプションが、YAMLの各項目に翻訳されています。

services:
   db-server:
      image: postgres:18.3-alpine3.23
      volumes:
         - my-db-data:/var/lib/postgresql
      networks:
         - db-net
      environment:
         - POSTGRES_PASSWORD=password
   db-client:
      image: adminer
      ports:
         - 8080:8080
      networks:
         - db-net
      depends_on:
         - db-server
volumes:
   my-db-data:
networks:
   db-net: 

この設定ファイルで作成されるシステムの構成を下図に示します。

ブラウザ画面上でAdminerのサーバー名にdb-serverと入れるだけで繋がるのは、Docker Composeがこの名前を自動的にDNSサーバーに登録してくれるためです。DNSはネットワーク内で「どの名前が、どのIPアドレスか」を紐づけます。これにより、Adminer(db-client)から「db-serverという名前の相手に繋いで」とリクエストを送ると、Docker内部のDNSサーバーが「それは172.x.x.xというIPアドレスのコンテナ」と教えてくれるため、通信が可能になります。

実行コマンド

Docker Composeを用いるときによく使用するコマンドを以下に示します。

システムの起動

設定ファイルをもとに、システムを起動します。

docker compose -f <定義ファイルのパス> up <オプション>

-fにより定義ファイルのパスを指定できます。指定しない場合はカレントディレクトリにあるdocker-compose.ymlを使用します。よく使うオプションとしては次のものがあります。

オプション説明
-dコンテナをバックグラウンドで実行します。このオプションを付けずに実行すると、コンテナのログがターミナルを占領し、ターミナルを閉じたりするとコンテナが止まってしまいます。
--build起動前に必ずイメージをビルドし直します。Dockerfileからのビルドを使っている場合、Docker Composeは一度ビルドしたイメージを使いまわそうとします。そのため、Dockerfileを書き換えても、単なるupだけでは古い環境のまま起動してしまうことがあります。このオプションを付与することで、そのような問題が発生しなくなります。環境構築の試行錯誤中や、ライブラリを追加した際によく使います。
-p <プロジェクト名>物理名を直接指定しない場合、物理名は「プロジェクト名_論理名」になりますが、このプロジェクト名を設定します。デフォルトではディレクトリ名がプロジェクト名になりますが、同じサーバー上で「開発用」「テスト用」など、同じ設定ファイルを使いつつ別々の環境として立ち上げたい場合に、プロジェクト名を明示して衝突を避けます。
--remove-orphans設定ファイルに記載されていない古いコンテナ(orphans=孤児)を自動的に検知して削除します。開発を進める中でdocker-compose.ymlから特定のサービスの定義を消すことがあります。通常のupでは、定義から消えた古いコンテナがそのまま残り続けますが、このオプションを使うことでクリーンアップ(削除)できます。

システムの一括停止・削除

upで作成したリソース(コンテナやネットワーク)を停止し、さらに削除まで行うコマンドです。

docker compose -f <定義ファイルのパス> down <オプション>

単なる停止(stop)ではなく、downを使う最大の理由は「環境を一度まっさらな状態に戻すため」です。よく使うオプションとしては次のものがあります。

オプション説明
--volumes (または-v)コンテナと一緒にボリュームも削除します。デフォルトのdownでは、名前付きボリューム(データベースの実体など)は削除されずに残ります。これは誤操作でデータを消さないための安全策ですが、開発中にデータベースを完全にクリアして、最初からやり直したい場合などには、このオプションを使用します。
--rmi <種類>イメージも削除します。種類にallを指定した場合は利用した全イメージ(つまり、外部から取得した公式イメージも含む)を削除します。localを指定した場合は、カスタムタグがないイメージ(build指定によって作成されたイメージ)のみを削除します。基本的には、--rmi localを使うことで、自分たちがビルドした古いイメージによる不具合(意図しない古いコードの混入など)を防ぐことができます。
--remove-orphans現在の設定ファイルには載っていないが、このプロジェクトに関連して過去に作成された孤児(orphans)コンテナを確実に検知して削除します。

起動状態の確認

Docker Composeで管理されているコンテナ群が期待通り動いているかを一覧表示します。

docker compose ps

STATUSで状態を確認します。Upは稼働中で、ポートの転送も生きています。Exited (0)は正常に終了しています。Exited (1)などの0以外の場合には、何らかのエラーで落ちています。期待通りにUpにならないときは、すかさず--allオプションを付けて、どのコンテナがいつ落ちたのかを確認するのがよいです。

オプション説明
-all(または-a止まっているコンテナも含めて表示します。デフォルトは実行中のコンテナのみを表示します。このオプションを使うことで、どのサービスが、どのような終了ステータスで止まっているかを確認できます。このため、トラブルシューティングに役立ちます。
--format json (または--format table出力をプログラムや目的に合わせて整形します。デフォルトは表形式です。jsonを指定することで、すべての情報をJSON形式で出力します。外部ツールやスクリプトで状態を監視したい場合によく使われます。
--servicesコンテナの物理的な名前ではなく、docker-compose.yml内で定義したサービス名(論理名)だけを一覧で表示します。サービス名が必要な他のコマンドを使う前に、サービス名を確認するときや、シェルスクリプトで特定のサービスだけを再起動するような自動化処理を組む場合に使います。

実行ログの確認

Dockerコンテナの中で何が起こっているか(エラーメッセージやデバッグ出力)を確認します。

docker compose logs <オプション>

バックグラウンド(-d)で起動した際、プラウザにエラーが出てもなぜ動かないかが分かりません。その際に、真っ先に叩くのがこのコマンドです。よく使うオプションとしては次のものがあります。

オプション説明
-fログをリアルタイムで追います。ターミナルが待ちの状態になり、アプリケーションが出力するログをリアルタイムで次々に表示します。開発中のデバッグで特に使用します。
--tail <行数>ログを直近の必要な分だけ表示します。
-tログの各行の先頭にDockerが管理している正確な時刻を付与します。タイミングが重要なバグを追う際に効果的です。

すべてのコンテナのログが混ざって見づらいときには、コマンドの最後にサービス名を書くことで、特定のコンテナのログだけ確認できます。

docker compose logs <オプション> <サービス名>

システムの停止

upで作成したリソース(コンテナやネットワーク)を削除せずに、停止します。

docker compose stop <オプション>

downと異なり、コンテナを停止させるだけで、ネットワークは維持されます。このため、一時的な中断に使用します。デフォルトでは、Dockerはコンテナに停止シグナルを送り、10秒待っても止まらない場合に強制終了させます。データベースなどの終了処理に時間がかかるサービスを扱っている場合、デフォルトの10秒では足りず、データが破損するリスクがあるため、その場合には-t <秒>オプションを使って、有路時間を変更できます。このオプションは、安全に、かつ確実にシャットダウンさせたいシステムを扱う際に重宝します。

stop で止めたら up でも start でも再開できますが、基本的には常に docker compose up -d を使う癖をつけておくのが、設定の反映漏れを防ぐ一番安全な方法です。docker compose startでも再開でき、若干速くなりますが、この場合にはdocker-compose.ymlを変更していた場合、その変更が反映されません。

【実践】Docker Composeで環境を起動してみる

前述のdocker-compose.ymlのサンプルをフォルダ(test)にコピーして、その中に移動した後にシステムを起動します。

cd test
docker compose up -d

ブラウザを開き、http://localhost:8080にアクセスするとデータベース管理マネージャーのログイン画面が表示されます。

Docker Composeの起動状態を確認してみます。

docker compose ps

NAME               IMAGE                      COMMAND                   SERVICE     CREATED         STATUS         PORTS
test-db-client-1   adminer                    "entrypoint.sh docke…"   db-client   2 minutes ago   Up 2 minutes   0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp
test-db-server-1   postgres:18.3-alpine3.23   "docker-entrypoint.s…"   db-server   2 minutes ago   Up 2 minutes   5432/tcp

STATUSを見るとUpになっており、PostgreSQLとデータベース管理ツール(Adminer)が起動していることを確認できます。また、物理名(NAME)を見ると、論理名の前にプロジェクト名としてフォルダ名(test)がついていることを確認できます。また、SERVICEを見ると、サービス名(論理名)が設定ファイルで指定した名前(db-clientdb-server)になっていることを確認できます。

次に、設定ファイルに記述したネットワークとボリュームが存在しているかを確認してみます。

docker network ls

NETWORK ID     NAME          DRIVER    SCOPE
043b667e26b6   bridge        bridge    local
eff6adf01512   host          host      local
2e6c6af3fe23   none          null      local
1714d0ca723e   test_db-net   bridge    local
docker volume ls

DRIVER    VOLUME NAME
local     test_my-db-data

これにより、設定ファイルに記述されたネットワークと名前付きボリュームが作成されたことを確認できました。さらに、設計図通りにリソースが紐づいているかをより詳細に確認したい場合にはinspectを使用します。

docker network inspect test_db-net
docker volume inspect test_my-db-data

例えば、割り当てられたIPアドレスやボリュームのマウントポジションなどを確認できます。

さらに、ログをリアルタイムで表示してみます。

docker compose logs -f

このコマンドを実行した状態で、ブラウザをF5で更新すると、リアルタイムでログが追加されることを確認できます。

...
db-client-1  | [Sat May  9 13:26:24 2026] [::ffff:172.18.0.1]:56070 Accepted
db-client-1  | [Sat May  9 13:26:24 2026] [::ffff:172.18.0.1]:56070 [200]: GET /
db-client-1  | [Sat May  9 13:26:24 2026] [::ffff:172.18.0.1]:56070 Closing

次に、システムを停止して削除してみます。

docker compose down

停止したサービスを含めて状態を確認してみると、コンテナが削除されていることを確認できます。

docker compose ps --all

NAME      IMAGE     COMMAND   SERVICE   CREATED   STATUS    PORTS

また、docker volume lsdocker network lsを実行してみると、ネットワークは削除されていますが、名前付きボリュームは削除されずに残っていることを確認できます。

docker volume ls

DRIVER    VOLUME NAME
local     test_my-db-data
docker network ls

NETWORK ID     NAME      DRIVER    SCOPE
043b667e26b6   bridge    bridge    local
eff6adf01512   host      host      local
2e6c6af3fe23   none      null      local

以上で、基本的なコマンドの動作を確認できました。

残ったイメージとボリュームは以下のコマンドで削除できます。

docker image rm adminer postgres:18.3-alpine3.23
docker volume rm test_my-db-data

補足事項①:セキュリティの向上

.envファイルの活用

前述のdocker-compose.ymlでは、データベースのパスワードを直接ファイル内に記述していましたが、パスワードを直接設定ファイルに書くのはリスクがあります。

具体的には、設定ファイルをGithubなどのソースコード管理ツールにプッシュすると、チームメンバーや公開設定によっては全世界にパスワードが公開されてしまいます。また、開発環境、本番環境でパスワードを変えたい場合、その都度YAMLファイルを書き換える必要があります。

これらのリスクや手間を避けるため、Docker Composeには、環境変数を別ファイル(.env)から読み込む仕組みがあります。

まず、docker-compose.ymlと同じフォルダに.envという名前のファイルを作成し、環境変数として機密情報を記述します。

DB_PASSWORD=password123

次に、docker-compose.ymlのパスワードの部分を${変数名}という形式に書き換えます。

environment:
   - POSTGRES_PASSWORD=${DB_PASSWORD}

このように設定ファイルと機密情報(.env)を分離することで、.envファイルさえGitの管理対象から外しておけば、安全に開発を行うことができます。すなわち、.gitignoreファイルに.envと記述し、絶対にリポジトリに含めないようにします。代わりに、チーム用には.env.example(値が空のテンプレート)を共有するのが定石です。また、環境ごとに.envの中身を書き換えるだけで、同じ設定ファイルを使いまわせるようになります。

外部通信の遮断

デフォルトでは、Dockerのネットワークはコンテナからインターネットへの外向きの通信を許可しています。しかし、データベースのような機密情報を扱うコンテナが、勝手に外部(クラッカーのサーバーなど)と通信できてしまうのは、セキュリティ上のリスク(データ流出の経路)になります。

このため、外部に公開する必要のないデータを扱うネットワークは外部から遮断するのがセキュリティレベルを上げるのに有効な手段です。外部から遮断するためには、対象のネットワークに対して、internalを設定します。

networks:
   db-net:
      internal: true # このネットワークに属するコンテナは外(インターネット)に出られない

これにより、万が一、脆弱性を突かれてコンテナを乗っ取られたとしても、ハッカーが外部のサーバーからスクリプトをダウンロードしたり、データを外部へ送信したりすることを物理的に遮断できます。

また、必要なポート以外は開けないというのも重要です。

ネットワークの分割

サービスが増えた場合は「表(Web)」と「裏(データベース)」でネットワークを分割するのがよいです。例えば、Webアプリとユーザーの間の通信をfrontend-net、Webアプリとデータベースの間の通信をbackend-netとするなどです。

これにより、ユーザーが触れるWebアプリが乗っ取られても、DBへは特定のネットワーク経由でしか辿りつけないため、攻撃を最小限に食い止められます。

リソース制限

DoS対策として、一つのコンテナが暴走してPC(サーバー)全体のCPUとメモリを使い果たさないように、上限を決めます。

services:
   [論理名]:
      deploy:
         resources:
            limits:
               cpus: '0.5'    # CPUを最大50%までに制限
               memory: 512M   # メモリを最大512MBまでに制限

特権の制限

コンテナ内のプログラムが、実行中に勝手に管理者権限(root)に昇格するのを防ぎます。

services:
   [論理名]:
      security_opt:
         - no-new-privileges:true

ボリュームの読み取り専用化

設定ファイルなど、書き換える必要がない場所は「読み取り専用」でマウントします。マウントポイントの指定において、末尾に:roを追加します。

services:
   [論理名]:
      volumes:
         - [ボリューム名]:[コンテナのパス]:ro

補足事項②:depends_onの制約

depends_onはコンテナを立ち上げる順番を制御するだけで、中身のソフトが使える状態になったかまでは見てくれません。

データベースは、コンテナが「Up(起動中)」になってから、内部でデータの初期化が終わるまで数秒の時間がかかります。そのため、起動直後にAdminerでログインしようとすると、接続エラーが発生することがあります。

手動リトライ

最もシンプルな方法は、手動でリトライする方法で開発環境ではこれで十分なことが多いです。

接続エラーが出ても焦らず、5~10秒ほど待ってからブラウザの更新ボタン(F5)を押します。データベースの準備が整えば、そのままつながります。

ヘルスチェックによる自動待機

データベースが本当に準備OKになるまで、Adminerの起動を待たせるという設定をDocker Composeの設定ファイルに書き込む方法です。

services:
  db-server:
    image: postgres:18.3-alpine3.23
    # DBが生きているかを確認するコマンドを指定
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s   # 5秒おきにチェック
      timeout: 5s    # 5秒でタイムアウト
      retries: 5     # 5回失敗したら「異常」とみなす
      start_period: 10s # 起動直後の10秒間は失敗をカウントしない

  db-client:
    image: adminer
    depends_on:
      db-server:
        condition: service_healthy # db-serverが「健康」になるまで待つ

healthcheckセクションは、診断ルールを定義するためのもので、診断される側(例:データベース)に記述する設定です。各項目の意味は次の通りです。

項目説明
test診断を実行するコマンドを指定します。["CMD-SHELL", "コマンド"]の形式で記述します。コンテナ内でこのコマンドが実行され、終了ステータスが0(成功)であれば「Healthy」、それ以外なら「Unhealthy」と判定されます。
interval診断を実行する間隔です。この間隔が短いほど準備完了を早く検知できますが、短すぎると診断自体がコンテナの負荷になります。
timeout一回の診断にかける最大待ち時間です。testコマンドがこの時間内に応答を返さない場合、その回の診断は失敗とみなされます。
retries異常と断定するまでの連続失敗回数です。起動直後は失敗するのが前提であるため、ここで指定した回数分だけ失敗を許容し、その間は起動中として扱われます。
start_period起動直後の猶予期間です。この期間中に行われた診断の失敗は、retriesのカウントに含めません。初期化に時間がかかる場合に指定します。

本記事の例では、PostgreSQLを使用しているため、今、クエリを受け付けられているかを尋ねるためのpg_isreadyコマンドを使用しています。例えば、データベースの起動に30秒かかる環境であれば、interval: 10sretries: 3以上の猶予を持たせるなどのように数値を調整する必要があります

depends_onconditionは、待機する側(例:adminer)に記述する設定で、起動のトリガーを指定します。

condition: service_healthy

通常のdepends_onは対象コンテナのプロセス開始のみを条件としますが、このconditionを追加することで、対象コンテナのステータスがhealthyに変わるまで、自身の起動をブロックするという制御が可能になります。

設定ファイルの修正

補足事項に書かれた内容を反映した最終的なdocker-compose.ymlは次のようになります。

services:
   db-server:
      image: postgres:18.3-alpine3.23
      deploy:
         resources:
            limits:
               cpus: '0.5'
               memory: 512M
      volumes:
         - my-db-data:/var/lib/postgresql
      networks:
         - backend-net # 隔離ネットワークを使用する
      environment:
         - POSTGRES_PASSWORD=${DB_PASSWORD}
      security_opt:
         - no-new-privileges:true
      healthcheck:
         test: ["CMD-SHELL", "pg_isready -U postgres"]
         interval: 5s
         timeout: 5s
         retries: 5
         start_period: 10s

   db-client:
      image: adminer
      ports:
         - 8080:8080
      networks:
         - frontend-net # ブラウザと話すための通常ネットワーク
         - backend-net  # DBと話すための隔離ネットワーク
      depends_on:
         db-server:
            condition: service_healthy

volumes:
   my-db-data:

networks:
   frontend-net: # 通常のネットワーク
   backend-net:
      internal: true # 隔離ネットワーク

起動コマンドは前述したとおりです。設定ファイルと.envファイルを作成した後に、それらのあるフォルダ内で次のコマンドを入力します。

docker compose up -d

ブラウザを開き、http://localhost:8080にアクセスするとデータベース管理マネージャーのログイン画面が表示されます。次のログイン情報を入力することでログインできます。

項目入力値指定場所(入力値根拠)
SystemPostgreSQL使用しているDockerイメージ(postgres)の種類に基づきます。Adminerは複数のDBに対応しているため、接続先エンジンの種類を選択します。
Serverdb-serverデータベースコンテナの起動時オプション--name db-serverで設定したデータベースコンテナの名前です。Dockerネットワーク内では、この名前がホスト名として解決されます。
Usernamepostgres公式postgresイメージのデフォルト設定です。環境変数-e POSTGRES_USERを指定しない場合、自動的にこの名前になります。
Passwordpassword123.env内の環境変数DB_PASSWORDに記述した文字列(password123)です。

これで、.envで指定されたパスワードでログインできることも確認できます。

今回の例の場合、.envでパスワードを変更してもdownではボリュームが消えないことと、Postgresは初回しか環境変数を見ないため、.envの変更内容が反映されません。反映させたい場合はデータベースに入り、直接パスワードを変更するしかありません(docker compose down -vでボリュームごと削除すれば、.envの変更内容が反映されますが、その場合データベースが完全に削除されてしまいます)。

まとめ

本記事では、Docker Composeを利用して複数のコンテナをひとつのシステムとして統合管理する方法を解説しました。

単にコンテナを並べるだけではなく、以下のポイントも取り扱いました。

  • IaC(Infrastructure as Code):docker-compose.ymlというひとつの設計ファイルで、ネットワーク、ボリューム、環境変数を一括管理する方法。
  • 動作順序の信頼性確保:healthcheckconditionを組み合わせることで、データベースの準備が整うまで後続の起動を自動待機させる手順。
  • 多層防御:.envによる機密情報の分離、internalネットワークを用いた二重ネットワーク構成によるデータベースの物理的な隔離。
  • リソースの制御:deploy: limitsを設定し、特定のコンテナの暴走からホストマシンを守る安全装置の設定。

Docker Composeを使用することは、効率化以上に、誰がどこで実行しても、安全かつ確実に同じ環境が制限されるという品質保証の確保に役立ちます。

関連記事

Dockerの基礎とコンテナの起動・バインドマウント(Docker入門)

2026/5/10   

本記事では、Windows環境にDocker Desktopを導入し、ApacheコンテナでローカルのHTMLファイルを同期表示させるまでの手順をまとめました。インストールなどの初期設定から、基本操作、そしてバインドマウントの設定までを一通り解説します。

Dockerfileの書き方入門:独自イメージのビルドとマルチステージビルド(Docker入門)

2026/5/10   

Dockerfileの基本コマンドと役割、効率的なビルドのためのキャッシュの仕組みを解説します。Next.jsの実行を例に、ビルドオプションの使い方から.dockerignoreによる除外設定、マルチステージビルドによるイメージ作成の手順まで、一連の流れを解説した入門記事です。

名前付きボリュームとコンテナ間ネットワーク(Docker入門)

2026/5/10   

Dockerコンテナの「使い捨て」の強みを活かしつつ、データの永続化とコンテナ間通信を実現する方法を解説。名前付きボリュームとバインドマウントの使い分け、ユーザー定義ネットワークによる名前解決の仕組みを、Adminerを使った実践ハンズオン形式で学べます。

Docker Composeの導入:複数コンテナの構成管理と起動の自動化(Docker入門)

2026/5/10   

Docker Composeを用いた複数コンテナの構成管理と、システム起動の自動化について解説します。単体のコンテナを動かす段階から先に進み、Webサーバー、データベース、管理ツールといった複数のサービスが連携するシステムを、一つの設定ファイル(docker-compose.yml)で制御するための方法を示します。また、セキュリティ対策(.env)やコンテナ同士の連携を保証するヘルスチェック(自動待機)などについても取り扱います。

-TECHNOLOGY
-