Docker 入門

Docker Compose によるマルチコンテナアプリケーションのビルド・実行・管理

docker-compose.yml ファイルでマルチコンテナアプリケーションのサービス、ネットワーク、およびボリュームを綿密に定義した後の重要なステップは、それらの定義を「機能」させることです。

Docker Compose は、アプリケーションスタック全体のライフサイクル管理を簡素化する直感的なコマンドを提供します。サービス用のカスタムイメージのビルドから、それらの起動のオーケストレーション、実行状態の管理、そしてグレースフルなシャットダウンとクリーンアップに至るまでをカバーします。本章では、これらのコアコマンドについて解説します。数行のシンプルなコードを叩くだけで、宣言型の Compose ファイルを完全に稼働するマルチコンテナアプリケーションへと変換し、アプリケーションスタック全体のビルド、実行、インスペクト、そして破棄をシームレスに実現できます。

1. docker compose up を使用した起動のオーケストレーション

docker compose up は、docker-compose.yml で定義されたマルチコンテナアプリケーションを実行するための基石となるコマンドです。このコマンドを実行すると、Docker Compose は設定をパースし、必要なリソース(存在しないネットワークやボリュームなど)の作成を処理し、指定されたカスタムサービスのイメージをビルドします。その後、正しい順序ですべてのサービスを起動し、それらの間の依存関係を適切に管理します。

1.1 docker compose up の仕組み

  • docker-compose.yml のパース: Compose はまず YAML ファイルをパースし、アプリケーション全体の構造を把握します。
  • リソースの作成: YAML で宣言された名前付きネットワークやボリュームが存在するかどうかを確認します。存在しない場合は自動的に作成します。
  • イメージのビルド: build コンテキスト(つまり、イメージの作成に Dockerfile を使用することを意味します)が指定されているすべてのサービスに対して、Compose はそのイメージをビルドします。イメージがローカルにすでに存在し、Dockerfile やビルドコンテキストに変更がない場合、明示的に再ビルドを要求しない限り、Compose は既存のイメージをそのまま使用します。
  • サービスの作成と起動: 次に、Compose は各サービス用のコンテナを作成し、それらを定義されたネットワークに接続し、指定に従ってボリュームをマウントします。明示的な depends_on の宣言や暗黙的なネットワークの依存関係に基づいて、インテリジェントな順序でサービスを起動します。
  • ログの記録: デフォルトでは、docker compose up は実行中のすべてのサービスの出力にアタッチし、それらのログを直接ターミナルにストリーミングします。これはデバッグや初期の動作確認に非常に優れています。

1.2 docker compose up のコアオプション

  • -d または --detach: コンテナをバックグラウンドで実行し、ターミナルを解放します。これは本番環境へのデプロイや、アプリケーション起動後もターミナルを使い続けたい場合に不可欠です。
  • --build: イメージがすでに存在し、変更されていない場合でも、build ディレクティブを持つサービスに対して Compose にイメージの再ビルドを強制します。ビルドコンテキスト内の Dockerfile やアプリケーションコードを変更したものの、Compose のキャッシュが再ビルドを妨げている可能性がある場合に非常に有用です。
  • --no-recreate: サービスのコンテナがすでに存在する場合、このオプションは Compose がそれらを再作成するのを防ぎます。既存のコンテナを直接起動しようと試みます。再作成が必要な設定変更を行っていない場合、コンテナの状態を保持するのに役立ちます。
  • --force-recreate: 設定が変更されていなくても、Compose にコンテナの停止と再作成を強制します。これは、クリーンな起動環境を確保したり、通常の再ビルドがトリガーされないような微小な環境変更を適用したりするのに役立ちます。
  • --remove-orphans: Compose ファイルで定義されなくなったものの、依然として存在しているサービスのコンテナを削除します。docker-compose.yml をリファクタリングして古いサービスを削除した際に、環境をクリーンに保つのに役立ちます。

1.3 例:データベースを含む Web アプリケーションの起動

PostgreSQL データベースに接続するシンプルな Web アプリケーションという一般的なシナリオを考えてみましょう。

プロジェクト構成:

my-web-app/
├── docker-compose.yml
├── web/
│   ├── Dockerfile
│   ├── app.py
│   └── requirements.txt

web/Dockerfile:

# 公式の Python 実行環境をベースイメージとして使用
FROM python:3.9-slim-buster

# コンテナ内の作業ディレクトリを設定
WORKDIR /app

# requirements.txt をコピーして必要なパッケージをインストール
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# アプリケーションの残りのコードをコピー
COPY . .

# Flask アプリケーションの 5000 番ポートを公開
EXPOSE 5000

# 環境変数を定義
ENV FLASK_APP=app.py

# Flask アプリケーションを実行
CMD ["flask", "run", "--host=0.0.0.0"]

web/requirements.txt:

Flask
psycopg2-binary

web/app.py:

from flask import Flask
import os
import psycopg2

app = Flask(__name__)

@app.route('/')
def hello():
    db_host = os.environ.get('DB_HOST', 'db') # 'db' は docker-compose におけるサービス名
    db_name = os.environ.get('POSTGRES_DB', 'mydatabase')
    db_user = os.environ.get('POSTGRES_USER', 'myuser')
    db_password = os.environ.get('POSTGRES_PASSWORD', 'mypassword')

    try:
        conn = psycopg2.connect(host=db_host, database=db_name, user=db_user, password=db_password)
        cur = conn.cursor()
        cur.execute("SELECT version();")
        db_version = cur.fetchone()[0]
        cur.close()
        conn.close()
        return f"Hello from Flask! PostgreSQL バージョンに接続成功: {db_version}"
    except Exception as e:
        return f"Hello from Flask! データベースに接続できません: {e}"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

docker-compose.yml:

version: '3.8'
services:
  web:
    build: ./web # Compose に './web' ディレクトリ内の Dockerfile を使用してイメージをビルドするよう指示
    ports:
      - "5000:5000" # ホストマシンの 5000 番ポートをコンテナの 5000 番ポートにマッピング
    environment:
      # これらの環境変数は web サービスのコンテナに渡される
      - DB_HOST=db # ホスト名 'db' は Docker ネットワーク内で 'db' サービスとして名前解決される
      - POSTGRES_DB=mydatabase
      - POSTGRES_USER=myuser
      - POSTGRES_PASSWORD=mypassword
    depends_on:
      # 'db' サービスが 'web' より先に起動することを保証。
      # 注意:これは起動順序のみを保証し、サービスが準備完了であることを保証するものではない。
      - db
    networks:
      - app-network # web サービスを 'app-network' に接続

  db:
    image: postgres:13 # Docker Hub の公式 PostgreSQL 13 イメージを使用
    environment:
      # 初期設定用の PostgreSQL 専用環境変数
      POSTGRES_DB: mydatabase
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
    volumes:
      # PostgreSQL データを永続化し、コンテナ再構築時のデータ消失を防ぐボリューム
      - db-data:/var/lib/postgresql/data
    networks:
      - app-network # db サービスを 'app-network' に接続

networks:
  app-network: # アプリケーションサービス用のカスタムブリッジネットワークを定義

volumes:
  db-data: # データベース永続化用の名前付きボリュームを定義

このアプリケーションを実行するには、ターミナルで my-web-app ディレクトリに移動し、以下を実行します。

docker compose up

ターミナルには webdb サービスからのログが継続的に流れてくるのが見えます。バックグラウンドで実行したい場合(日常的な開発やデプロイではこちらが一般的です)、-d を追加します。

docker compose up -d

これでサービスはバックグラウンドで実行されます。ブラウザを開いて http://localhost:5000 にアクセスすると、Web アプリケーションを確認できます。

2. docker compose build を使用した明示的なビルド

docker compose upbuild コンテキストを持つサービスのイメージビルドを自動的に処理しますが、特定のシナリオでは、アプリケーションスタック全体を必ずしも起動せずに、明示的にイメージをビルドまたは再ビルドしたい場合があります。このような時に docker compose build コマンドを使用します。

2.1 いつ docker compose build を使用するべきか

  • CI/CD 事前ビルド: 継続的インテグレーション/継続的デプロイメント(CI/CD)パイプラインにおいて、すべてのイメージのビルドを独立したステップとして実行し、その後それらをイメージレジストリにプッシュしたりデプロイしたりしたい場合があります。
  • Dockerfile のデバッグ: Dockerfile をアクティブに開発およびデバッグしている際、アプリケーションの起動を待たずにイメージのみをビルドすることで、ビルドプロセスをより高速にイテレーションできます。
  • 強制的な再ビルド: キャッシュされたイメージに問題がある疑いがある場合や、完全にクリーンなビルドを確実に取得したい場合、docker compose build を使用することで直接的なコントロールが可能になります。

2.2 docker compose build のコアオプション

  • --no-cache: イメージのビルドプロセス中にキャッシュレイヤーを使用しません。これにより、Docker は Dockerfile 内のすべての命令を最初から実行するように強制されます。クリーンなビルドを保証したり、新しいベースイメージをプルしたりする際に非常に有用です。
  • --pull: DockerfileFROM 命令で参照されているイメージの最新バージョンを常にプルしようと試みます。これにより、ベースイメージが常に最新状態であることが保証されます。
  • [SERVICE...]: docker-compose.yml で定義されたすべてのサービスをビルドするのではなく、特定のサービスのイメージのみをビルドするために、1つまたは複数のサービス名を指定できます。

2.3 例:特定のサービスをビルドする

先ほどの my-web-app の例を引き続き使用します。

web サービスのイメージのみをビルドしたい場合:

docker compose build web

docker-compose.ymlbuild 命令を持つすべてのサービスイメージをビルドし、キャッシュを使用せずに最新のベースイメージを強制的にプルしたい場合:

docker compose build --no-cache --pull

このコマンドは、キャッシュレイヤーを無視して web サービスのイメージを再ビルドし、Docker Hub に更新されたバージョンがある場合は、最新の python:3.9-slim-buster ベースイメージを確実にプルします。

3. 実行中のマルチコンテナアプリケーションの管理

アプリケーションが稼働し始めたら、様々なコマンドを使用してその状態を検査し、ログを確認し、サービスのライフサイクルを制御する必要があります。Docker Compose は、これらの管理タスクを完了するための包括的なコマンドセットを提供しています。

3.1 docker compose ps を使用したサービス状態の確認

docker compose ps コマンドは、Compose アプリケーションに関連付けられているコンテナをリストアップし、それらの状態、ポート、および実行中のコマンドを表示します。

docker compose ps

期待される出力(例):

NAME                    IMAGE                         COMMAND                  SERVICE    CREATED        STATUS        PORTS
my-web-app-db-1         postgres:13                   "docker-entrypoint.s…"   db         2 minutes ago   Up 2 minutes   5432/tcp
my-web-app-web-1        my-web-app-web                "flask run --host=0.…"   web        2 minutes ago   Up 2 minutes   0.0.0.0:5000->5000/tcp

この出力は、dbweb サービスが両方とも正常に実行されている(Up)ことを明確に示しており、同時にそれらのイメージ、コマンド、ポートマッピングの状況も表示しています。

3.2 docker compose logs を使用したログの確認

docker compose logs コマンドは、サービスのログ出力を表示するために使用されます。デフォルトでは、すべてのサービスのログが表示されます。

docker compose logs

コアオプション:

  • -f または --follow: ログ出力をフォローし、新しいログをリアルタイムでストリーミングします(tail -f に似ています)。
  • --tail N: 各コンテナの最新 N 行のログのみを表示します。
  • [SERVICE...]: 特定のサービスのログのみを確認するために、1つまたは複数のサービス名を指定します。

web サービスのログを専門に確認し、リアルタイムで追跡したい場合:

docker compose logs -f web

これはリアルタイムのデバッグとモニタリングにおいて極めて有用です。

3.3 docker compose stop を使用したサービスの停止

docker compose stop コマンドは、実行中のサービスコンテナをグレースフルに停止させますが、それらを削除することはありません。後で docker compose start を使用してこれらのコンテナを再起動することができます。

docker compose stop

特定のサービスを指定して停止することもできます:

docker compose stop web

3.4 docker compose start を使用した停止済みサービスの起動

docker compose stop を使用してサービスを停止した場合、docker compose start を使用してそれらを再起動させることができます(コンテナがまだ存在していることが前提です)。

docker compose start

または特定のサービスを起動します:

docker compose start web

3.5 docker compose restart を使用したサービスの再起動

docker compose restart コマンドは、サービスを停止してから再起動します。これは、サービスの再起動が必要な変更を適用するための便利な方法です。

docker compose restart

または特定のサービスを再起動します:

docker compose restart web

3.6 docker compose exec を使用したサービス内でのコマンド実行

docker compose exec コマンドを使用すると、実行中のサービスコンテナ内で任意のコマンドを実行できます。これはデバッグ、データベースマイグレーションの実行、または管理タスクの実行において計り知れない価値があります。

docker compose exec [SERVICE] [COMMAND]

web サービスのコンテナ内でインタラクティブな bash ターミナルを開きたい場合:

docker compose exec web bash

コンテナ内に入ると、ファイルを検査したり、環境変数を確認したり、アプリケーション固有のコマンドを実行したりできます。exit と入力するとコンテナのターミナルを終了します。

db サービスのコンテナ内で SQL クライアントを実行したい場合:

docker compose exec db psql -U myuser mydatabase

これにより、PostgreSQL データベースと直接インタラクトできるようになります。

3.7 docker compose down を使用したアプリケーションの破棄

docker compose down コマンドは、実行中のコンテナを停止し、docker compose up によって作成されたコンテナ、ネットワーク、および(デフォルト設定の)デフォルトネットワークを削除します。

docker compose down

コアオプション:

  • --volumes または -v: docker-compose.ymlvolumes セクションで宣言された名前付きボリュームと、すべての匿名ボリュームを削除します。特にデータベースの場合、永続化されたデータが完全に削除されるため、このオプションの使用には十分注意してください。
  • --remove-orphans: Compose ファイルで定義されなくなったものの、まだ実行されている可能性があるサービスのコンテナを削除します。
  • --rmi all: キャッシュされたイメージも含め、いずれかのサービスによって使用されているすべてのイメージを削除します。(--rmi local はカスタムタグのないローカルイメージのみを削除します)。

アプリケーションを破棄すると同時に db-data の名前付きボリュームを削除したい場合(これによりデータベースのデータが削除されます):

docker compose down --volumes

強い注意喚起: --volumes オプションなしの docker compose down は、(db-data のような)名前付きボリュームを保持します。これは、再度 docker compose up を実行した場合に、データベースのデータがそのまま存在することを意味します。これは開発環境において一般的かつ推奨されるプラクティスです。完全に新しいデータの状態にリセットしたいと明確に意図した場合にのみ、--volumes を使用してください。

4. アプリケーションの依存関係とオーケストレーション

docker-compose.yml 内の depends_on キーワードは、サービスの起動順序をオーケストレーションする上で極めて重要な役割を果たします。docker compose up を実行すると、Compose はこの情報を利用し、ターゲットのサービスを起動する前に、それが依存するサービスを確実に先に起動します。

私たちの例では:

services:
  web:
    # ...
    depends_on:
      - db

これは Docker Compose に、db サービスを最初に起動しなければならないこと、そして db が実行されている(コンテナが起動したことを意味します)状態になって初めて web サービスの起動を継続することを伝えています。

4.1 depends_on の限界を理解する

depends_on の重要な限界を理解しておく必要があります。それは、依存関係として指定されたコンテナがすでに起動していることのみを保証するということです。コンテナ内のアプリケーションの準備が整うこと、ヘルスステータスが正常になること、または接続の待機を開始することまでは待ちません。

例えば、depends_on: - db は PostgreSQL コンテナが実行されていることは保証しますが、そのコンテナ内の PostgreSQL サーバーが完全に初期化され、データベースが作成され、クライアントの接続を受け入れる準備が整っていることまでは保証できません。

本番レベルのアプリケーションでは、通常、より堅牢な準備完了チェック(Readiness Checks)が必要になります。本章は基礎的なビルドと実行に焦点を当てていますが、真に弾力性のあるマルチコンテナアプリケーションを構築するためには、多くの場合 healthcheck(ヘルスチェック、これはより高度な Docker Compose のトピックです)や、依存関係の準備が整ったかどうかをポーリングするカスタムのエントリポイントスクリプト(Entrypoint Scripts)を使用する必要があることを知っておいてください。しかし、多くの開発シナリオにおいては、depends_on が提供する基本的なオーケストレーションで十分です。

5. 実践デモ

ここで学んだコマンドを使って、my-web-app の完全なライフサイクルを通して実演してみましょう。

5.1 ステップ 1: 初回セットアップと実行(クリーンスタート)

前述した docker-compose.ymlweb/Dockerfileweb/app.py、および web/requirements.txt ファイルを含む my-web-app プロジェクトディレクトリがあると仮定します。

そのディレクトリに移動します:

cd my-web-app

バックグラウンドモードでアプリケーションを起動します。これにより web イメージがビルドされ、app-network ネットワークと db-data ボリュームが作成された後、dbweb コンテナが起動します。

docker compose up -d

ネットワーク、ボリューム、およびコンテナの作成を示す出力の後に、Started に似たメッセージが表示されるはずです。

サービスが実行されているか検証します:

docker compose ps

両方のサービスの状態が Up と表示されるはずです。

ログを確認し、すべてが正常に起動しているか確かめます:

docker compose logs

PostgreSQL サーバーの準備が完了したことと、Flask アプリケーションが 5000 番ポートで実行されていることを示すメッセージを探します。

ブラウザで http://localhost:5000 にアクセスします。「Hello from Flask! PostgreSQL バージョンに接続成功: ...」と表示されるはずです。

5.2 ステップ 2: コードの変更と再ビルド

app.py を修正して表示されるメッセージを変更してみましょう。

web/app.py を編集します:

# ... (既存のインポートとアプリケーション定義を保持)

@app.route('/')
def hello():
    # ... (データベース接続パラメータを保持)

    try:
        # ... (データベース接続ロジックを保持)
        return f"変更された Flask アプリからの挨拶!PostgreSQL バージョンに接続成功: {db_version}" # ここでメッセージを変更
    except Exception as e:
        return f"変更された Flask アプリからの挨拶!データベースに接続できません: {e}"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

アプリケーションコードを変更したため、web サービスのイメージを再ビルドし、コンテナを再起動する必要があります。

まず、web サービスを停止します(db サービスのイメージは変更されていないため、実行したままで構いません):

docker compose stop web

次に、web イメージを再ビルドします。--no-cache を使用して、最新の app.py がイメージに含まれることを確実にします。

docker compose build --no-cache web

最後に、web サービスを再起動します。シンプルに言えば、ビルド後に web サービスに対して再度 up コマンドを実行するだけです。

docker compose up -d web

(あるいは、イメージのビルド後であれば、サービスを直接再起動するというより簡単な方法で変更を適用することもできます:docker compose restart web

ブラウザで http://localhost:5000 をリロードします。更新されたメッセージ「変更された Flask アプリからの挨拶!...」が表示されるはずです。

5.3 ステップ 3: exec を使用したデータベース操作の実行

docker compose exec を使用してデータベースとインタラクトします。PostgreSQL シェルに接続し、簡単なテーブルを作成してみましょう。

サービスが依然として実行されていることを確認します(docker compose ps)。

db コンテナの psql ターミナルに入ります:

docker compose exec db psql -U myuser mydatabase

これで PostgreSQL クライアント内部に入りました。いくつかの SQL コマンドを実行してみましょう:

CREATE TABLE messages (id SERIAL PRIMARY KEY, text VARCHAR(255));
INSERT INTO messages (text) VALUES ('データベースからの挨拶!');
SELECT * FROM messages;
\q

\q コマンドで psql ターミナルを終了します。これでホストマシンのターミナルに戻ります。

これは、docker compose exec が、管理やデバッグの目的でホストマシンやコンテナに直接ログインすることなく、どのようにして特定のサービスとのインタラクションを可能にするかを示しています。

5.4 ステップ 4: アプリケーションの破棄(データの保持)

開発を完了した際、あるいはアプリケーションを停止しつつも次回の起動のためにデータベースのデータを保持しておきたい場合:

docker compose down

これは webdb コンテナ、そして app-network ネットワークを停止および削除します。最も重要な点は、db-data の名前付きボリュームは削除されず、messages テーブルのデータが保持されるということです。

コンテナが消去されたか検証します:

docker compose ps

サービスは何もリストアップされないはずです。

5.5 ステップ 5: アプリケーションの再起動(永続化データ付き)

では、もう一度アプリケーションを起動してみましょう。down コマンドの際に --volumes を使用しなかったため、db-data ボリュームは依然として存在します。

docker compose up -d

Docker Compose はコンテナとネットワークを再作成します。db サービスは既存の db-data ボリュームを見つけ、それを使用します。もしアプリケーションがそのデータを表示するように設計されていれば、先ほど挿入したデータはそこにそのまま存在しています。

5.6 ステップ 6: 完全な破棄と全データのクリア

永続化されたデータの削除も含め、完全にクリーンな環境が必要な場合:

docker compose down --volumes

これはコンテナ、ネットワーク、そして db-data ボリュームを停止および削除します。messages テーブルやその他のすべてのデータベースのデータは永久に削除されます。

ボリュームが消去されたか検証します:

docker volume ls

my-web-app_db-data というボリュームはもはや表示されないはずです。