Docker 入門

Docker Compose 基礎

シンプルな単一コンテナのアプリケーションを構築する場合、docker runコマンドだけでコンテナの起動と設定に必要なすべての機能が提供されます。しかし、現実世界のアプリケーションがこれほどシンプルなケースは稀です。ほとんどのモダンなアプリケーションは、Webサーバー、アプリケーションのバックエンド、データベース、キャッシュ、およびその他の補助サービスといった、相互に接続された複数のサービスで構成されています。これらのサービスを個別のdocker runコマンドで単独に管理しようとすると、プロセスが煩雑になりエラーが発生しやすくなるだけでなく、異なる環境で一貫して再現することが非常に困難になります。

そこで、Docker Composeの出番となります。Docker Composeは、マルチコンテナのDockerアプリケーションを定義し、実行するためのツールです。単一のファイルでアプリケーションスタック全体を設定し、すべてのサービス、ネットワーク接続、データボリュームをオーケストレーションすることができます。本章では、Docker Composeのコア概念と、その設定ファイルの基本構造について解説します。この設定ファイルはYAML(YAML Ain't Markup Language)言語で記述されます。このYAMLファイルの構造を理解することが、複雑なマルチコンテナのDockerアプリケーションを効率的に管理するための基礎となります。

1. Docker Composeとその役割を理解する

構文について深く掘り下げる前に、なぜDocker Composeがそれほど重要なのかを理解する必要があります。前のモジュールで学んだように、Dockerを使用すると単一のコンポーネントをコンテナ化することができます。イメージ(Image)のビルド、コンテナの実行、ネットワークやデータボリュームの管理が可能です。しかし、以下のようなコンポーネントを含むアプリケーションがあると仮定しましょう。

  • Python Flask Webアプリケーション(コンテナ1)
  • PostgreSQLデータベース(コンテナ2)
  • Redisキャッシュ(コンテナ3)

もしComposeなしでこのアプリケーションを実行する場合、複数のdocker runコマンドを実行する必要があります。

  • PostgreSQL用(データ用の名前付きボリュームの作成も必要になる場合があります)
  • Redis用
  • Flaskアプリケーション用(データベースやキャッシュコンテナへのリンク、ポートの公開、関連コードのマウントが必要です)

このアプローチは、すぐに極めて複雑になってしまいます。

  • 手動オーケストレーション: 正しい順序でコンテナを手動で起動しなければなりません(例:まずデータベースを起動し、次にアプリケーションを起動する)。
  • 設定のドリフト: いずれかのdocker runコマンドでミスをしやすく、開発環境と本番環境の間で設定の差異が生じる原因になります。
  • 低い再現性: この環境をチームメンバーに共有するには、長大なスクリプトや一連の長いコマンドを共有する必要があります。
  • 煩雑なクリーンアップ: 関連するすべてのコンテナ、ネットワーク、データボリュームを停止して削除するには、大量のdocker stopdocker rmdocker network rmdocker volume rmコマンドを実行する必要があります。

Docker Composeは、単一のdocker-compose.ymlファイルでアプリケーションスタック全体を定義できるようにすることで、これらの問題を完璧に解決します。このファイルはブループリント(設計図)のようなもので、具体的に以下の内容を指定します。

  • Services(サービス): アプリケーションを構成するコンテナ群(例:Webアプリケーション、データベース、キャッシュ)。
  • Images(イメージ): 各サービスが使用するDockerイメージ。
  • Dependencies(依存関係): サービス間の起動順序関係(例:Webアプリケーションはデータベースに依存する)。
  • Networking(ネットワーク): サービス間でどのように通信を行うか。
  • Volumes(ボリューム): 各サービスの永続化データをどのように管理するか。

この1つのdocker-compose.ymlファイルがあれば、1つのコマンド(docker compose up)だけでアプリケーション全体を起動し、別のコマンド(docker compose down)でそれを停止できます。さらに、完全に一貫した環境設定を誰とでも共有することが可能になります。

2. Docker ComposeのYAML構文入門

Docker Composeの設定ファイルはYAMLで記述されます。YAML(YAML Ain't Markup Language:「YAMLはマークアップ言語ではない」の意)は、人間に極めて優しいデータシリアライズ標準であり、その優れた可読性から、設定ファイルの記述に頻繁に使用されています。JSONやXMLに触れたことがあれば、YAMLの概念がそれらと非常に似ていることに気づくでしょう。ただし、YAMLの構文は中括弧やタグではなく、インデント(字下げ)に強く依存しています。

基本的なYAML構文を理解することは、有効なDocker Composeファイルを記述する上で不可欠です。

2.1 コアとなるYAMLの概念

キーバリューペア (Key-Value Pairs): これは最も基本的な構成要素です。キー(Key)の後にコロン(:)が続き、その後にスペースを1つ空けて、最後にその値(Value)が来ます。

key: value

例:

name: My Application
version: 1.0

インデント (Indentation): YAMLはスペースによるインデントを使用して、構造と階層関係を表現します。インデントには必ずスペースを使用しなければならず、Tabキーは絶対に使用してはいけません。スペースの数は変更可能ですが、同一の論理ブロック内では一貫性を保つ必要があります。通常、各インデントレベルには2つまたは4つのスペースを使用します。インデントのミスは、YAMLファイルにおいて最もよくあるエラーの原因です。

parent_key:
  child_key_1: value_1
  child_key_2:
    grandchild_key: value_2

この例では、child_key_1child_key_2は内側に1階層インデントされているため、parent_keyの子要素となります。そして、grandchild_keychild_key_2の子要素です。

リスト / シーケンス (Lists / Sequences): リストは、各項目の先頭にハイフン(-)とスペースを配置することで表現します。

list_name:
  - item_1
  - item_2
  - item_3

リストの内部には、キーバリューペアや他のリストを含めることができます。例:

fruits:
  - apple
  - banana
  - orange

あるいは、マッピング(辞書)を含むリストになります。

people:
  - name: Alice
    age: 30
  - name: Bob
    age: 25

文字列 (Strings): YAMLでは、通常文字列に引用符を付ける必要はありません。ただし、文字列に特殊文字(:#&*!|>{[],? など)が含まれている場合や、特殊文字で始まる場合、あるいは数値やブール値(yesnotruefalseなど)として誤って解析される可能性がある場合は、シングルクォーテーション(')またはダブルクォーテーション(")で囲むことを強く推奨します。例:

unquoted_string: Hello world
quoted_string_1: "内部にコロンを含む文字列です: ここ"
quoted_string_2: '12345' # '12345'は文字列として解析されますが、12345は整数として解析されます

複数行の文字列については、ブロックスカラー(block scalars)を使用することができます。

  • |(リテラルブロックスカラー):すべての改行を保持します。
  • >(フォールディングブロックスカラー):改行を折りたたんでスペースに変換します(段落間に空行がある場合を除く)。例:
literal_text: |
  これは複数行の
  文字列です。
  改行はそのまま保持されます。

folded_text: >
  これも複数行の
  文字列です。しかし改行は
  折りたたまれてスペースに変換されます。

コメント (Comments): コメントはハッシュ記号(#)で始まり、その行の終わりまで続きます。

# これは行全体のコメントです
key: value # これは行末のコメントです

2.2 実践YAML例:基本構造

これらの概念を定着させるために、シンプルなYAMLファイルを見てみましょう。

# これは基本構文を示すサンプルYAMLファイルです。

# ルート階層にあるシンプルなキーバリューペア
application_name: MyAwesomeApp

# シンプルな文字列のリストを値として持つキー
technologies:
  - Docker
  - Python
  - PostgreSQL
  - Redis

# マッピング(辞書)を値として持つキー
settings:
  database:
    host: localhost
    port: 5432
    user: admin
  cache:
    enabled: true
    max_memory: '256mb' # '256mb'は純粋な数値ではないため、文字列値を使用します

# 複数行の文字列の例
description: |
  このアプリケーションは、モダンな
  技術スタックを活用して高性能と高スケーラビリティを実現しています。
  Web API、堅牢なデータベース、
  そして高速なキャッシュ層が含まれています。

この例では以下のようになっています。

  • application_nametechnologiessettingsdescriptionはすべてトップレベルキーです。
  • technologiesの値はリストです。
  • settingsの値はマッピングであり、その内部にネストされたマッピング(databasecache)を含んでいます。
  • 一貫したインデント方法に注目してください。hostportuserはすべて同じインデントレベルにあり、これらがdatabaseの子要素であることを示しています。

3. docker-compose.ymlファイルの構造

docker-compose.ymlファイルは、すべてのDocker Composeプロジェクトのコアです。単一のファイルであり、アプリケーションスタック内のすべてのサービス、ネットワーク、ボリュームを定義します。サービス、ネットワーク、ボリュームの具体的な設定については次の章で詳しく説明しますが、現段階で最も重要なのは、このファイルを構成する基礎的なトップレベルキー(Top-Level Keys)を理解することです。

3.1 docker-compose.ymlのトップレベルキー

典型的なdocker-compose.ymlファイルは、通常versionキーで始まり、少なくとも1つのservicesキーを含みます。必要に応じて、カスタム設定を行うためのnetworksvolumesを含めることもできます。

version:

  • 役割: Composeファイルフォーマットのバージョンを指定します。バージョンによってサポートされる機能や構文が異なるため、これは非常に重要です。
  • モダンなプラクティス: 常に3.xシリーズのバージョン(例:3.8、3.9)を使用します。2.xバージョンは古く、機能も十分ではありません。
  • 位置: ファイルの先頭(最初のキー)に配置する必要があります。
  • 例:
version: '3.8' # Composeファイルフォーマットのバージョン3.8を使用する

services:

  • 役割: ここは最も重要な部分です。ここでアプリケーションスタックを構成する個々のコンテナ(すなわちサービス)を定義します。このキーの下にある各サービスエントリは、Dockerコンテナをどのようにビルド、設定、実行するかについての定義を表しています。
  • 構造: servicesの下には、任意のサービス名(例:webappdatabasecache)を定義できます。それぞれのサービス名がキーとなり、その値として、その特定のコンテナに関するすべての設定詳細を含むマッピングが配置されます。
  • 位置: トップレベルキーであり、versionの直下(または同階層)に位置します。
  • 例(概念的構造 - 具体的な設定は次の章で解説します):
version: '3.8'

services: # このブロックでは、アプリケーションのすべてのコンテナを定義します。
  # 'services'の下にある各キーは、コンテナ/サービスの名前です。
  web_frontend: # これはWebユーザーインターフェースの定義です。
    # web_frontendサービスの設定詳細はここに配置され、
    # 2スペース(または採用した統一インデント幅)内側にインデントされます。
    # 例(ここでは展開しません): image, build, ports, volumes, environment, depends_on.

  api_backend: # これはバックエンドAPIサーバーの定義です。
    # api_backendサービスの設定詳細はここに配置されます。

  database: # これはデータベースサーバーの定義です。
    # databaseサービスの設定詳細はここに配置されます。

この例では、web_frontendapi_backenddatabaseはすべてユーザー定義のサービス名です。それらの具体的なすべての設定(使用するイメージや公開するポートなど)は、これらの名前の下にネストされます。

networks (オプション):

  • 役割: サービスに対するカスタムネットワークを定義します。デフォルトでは、Composeは自動的にデフォルトネットワークを作成しますが、カスタムネットワークはより優れた分離性とコントロールを提供します。
  • 構造: networksの下には、任意のネットワーク名を定義できます。それぞれのネットワーク名がキーとなり、その値として、ネットワークの設定詳細(driverなど)を含むマッピングが配置されます。
  • 位置: トップレベルキーであり、servicesと同階層になります。
  • 例(概念的構造):
version: '3.8'

services:
  # ... サービス定義 ...

networks: # このブロックは、サービス用のカスタムネットワークを定義します。
  # 後で明示的にサービスをこれらのネットワークに接続できます。
  app_network: # これはカスタムネットワーク名です。
    # app_networkの設定詳細はここに配置されます。
    # (例:driver, external, ipam)

volumes (オプション):

  • 役割: データの永続化保存に使用する名前付きボリューム(Named Volumes)を定義します。バインドマウント(Bind Mounts:ホストマシンのパスをコンテナにマウントする)は各個別のサービス内で設定されますが、名前付きボリュームは管理と再利用を容易にするため、通常ここでグローバルに定義されます。
  • 構造: volumesの下には、任意のボリューム名を定義できます。各ボリューム名がキーとなり、その値として、ボリュームの設定詳細(driverなど)を含むマッピングが配置されます。
  • 位置: トップレベルキーであり、servicesnetworksと同階層になります。
  • 例(概念的構造):
version: '3.8'

services:
  # ... サービス定義 ...

volumes: # このブロックは、サービス間で共有可能な名前付きボリューム、
  # またはデータの永続化保存に使用するボリュームを定義します。
  db_data: # これはデータベースデータを保存するための永続化データボリューム名です。
    # db_dataの設定詳細はここに配置されます。
    # (例:driver, external)

  app_logs: # これは別の名前付きボリュームで、おそらくアプリケーションログの保存に使用されます。
    # app_logsの設定詳細はここに配置されます。

3.2 docker-compose.ymlトップレベル構造のまとめ一覧

version: '3.8' # 常にversionキーで始めます。

services: # ここで独立したコンテナをサービスとして定義します。
  # 各サービスは1つのコンテナ(または同一コンテナのグループ)を表します。
  service_name_1:
    # --- service_name_1の設定はここに記述します ---
    # 例:image, ports, volumes, environment, networks, depends_on
  service_name_2:
    # --- service_name_2の設定はここに記述します ---
    # 例:image, build, command, restart, logging
  # ... その他のサービス ...

networks: # (オプション) サービス間通信用のカスタムネットワークを定義します。
  network_name_1:
    # --- network_name_1の設定はここに記述します ---
    # 例:driver, driver_opts, external
  network_name_2:
    # --- network_name_2の設定はここに記述します ---
    # 例:ipam, attachable
  # ... その他のネットワーク ...

volumes: # (オプション) データの永続化保存に使用する名前付きボリュームを定義します。
  volume_name_1:
    # --- volume_name_1の設定はここに記述します ---
    # 例:driver, driver_opts, external, labels
  volume_name_2:
    # --- volume_name_2の設定はここに記述します ---
    # 例:name
  # ... その他のデータボリューム ...

4. 構造例のデモンストレーション

これらの構造要素がどのように組み合わさって実際のdocker-compose.ymlファイルになるのかを見てみましょう。ここでは純粋に「構造」に焦点を当てており、各サービス、ネットワーク、ボリュームの内部にある具体的な設定オプションについては深く掘り下げないことに留意してください。

4.1 例1:基本的なWebサーバーのスケルトン

この例では、単一のWebサーバーサービス用のスケルトンを構築しています。imageディレクティブがコメントアウトされている点に注意してください。これは後で説明することを強調するためですが、その配置場所はYAML構造の一部となります。

# これはシンプルなWebサーバーアプリケーションのdocker-compose.ymlファイルです
# 本ファイルでは基本構造である version と単一サービス定義を実演しています。

version: '3.8' # Docker Composeファイルフォーマットのバージョンを指定します。
# 常に1行目に記述されるべきです。

services: # 'services'キーは、独立したすべてのコンテナ(サービス)を定義する場所です。
  # 'services'の下にある各キーは、単一のサービスを表します。
  web: # これはWebサーバーサービスに付けた名前です。
    # このキーの下に、'web'コンテナに関する具体的な設定をすべて定義します。
    # 例えば、どのDockerイメージを使用するか、どのポートを公開するかなどです。

    # image: nginx:latest # この行(および類似の行)は'web'の実際のイメージを定義します。
    # その完全な解説は次の章で紹介します。
    # 現在は、この設定がYAML構造の*どこに配置されるべきか*という点にのみ注目します。

    # ports:
    #   - "80:80" # これはポートマッピングを定義する例です。
    # ここでのインデントは非常に重要です:'- "80:80"'は'ports'という名前のリストの1項目であり、
    # 'ports'自体は'web'サービスの1つのプロパティです。

解説: この例では、versionservicesという2つのトップレベルキーを明確に示しています。servicesの下には、webという名前のサービスを定義しました。コメントで説明している通り、webのすべての具体的な設定(imageportsなど)は、その下にネストされ、インデントされています。

4.2 例2:マルチサービスアプリケーションのスケルトン(Webアプリ + データベース)

この例では構造を拡張して複数のサービスを含め、同時にカスタムのnetworksvolumesを提示し、それらがトップレベルのどの位置に配置されるかを示しています。

# これはマルチサービスアプリケーション(例:データベース付きWebアプリケーション)のdocker-compose.ymlファイルです
# 本ファイルは複数のサービスを含むトップレベル構造を実演し、
# カスタムネットワークとデータボリュームのプレースホルダー部分を含んでいます。

version: '3.8' # モダンなComposeファイルフォーマットのバージョンを使用します。

services: # このセクションでは、アプリケーション内のすべての異なる構成要素(コンテナ)を定義します。

  webapp: # このサービスは、メインのWebアプリケーションコンテナを表します。
    # そのすべての具体的な設定はここにネストされます。
    # 例えば、Dockerfileからビルドするように指定したり、
    # 環境変数を設定したり、リッスンするポートを指定したりします。

    # build: . # カレントディレクトリのDockerfileからイメージをビルドする例。
    # environment: # 環境変数を設定する例。
    #   DATABASE_URL: postgres://user:password@database:5432/mydb
    # depends_on: # サービスの依存関係を宣言する例。
    #   - database

  database: # このサービスは、データベースコンテナ(PostgreSQL、MySQLなど)を表します。
    # その具体的な設定はここにネストされます。
    # これにはデータベースイメージ、認証情報用の環境変数、
    # データ永続化のためのデータボリュームのマッピングなどが含まれます。

    # image: postgres:13 # Dockerイメージを指定する例。
    # environment:
    #   POSTGRES_DB: mydb
    #   POSTGRES_USER: user
    #   POSTGRES_PASSWORD: password
    # volumes:
    #   - db_data:/var/lib/postgresql/data # 名前付きボリュームをマウントする例。

# --- オプションのトップレベル階層部分 ---
# これらのセクションは常に必須というわけではありませんが、
# 高度なネットワーク設定や永続化データ管理によく使用されます。

# networks: # このセクションはカスタムネットワークを定義します。
#   # 後で明示的にサービスをこれらのネットワークに接続し、
#   # デフォルトネットワークよりも優れた分離性と組織構造を提供できます。
#
#   app_internal_network: # カスタムネットワーク名。
#     # driver: bridge # ネットワークドライバを指定する例。
#     # ipam: # IPアドレス管理(IPAM)の設定例。
#     #   config:
#     #     - subnet: 172.20.0.0/16

# volumes: # このセクションは永続化ストレージ用の名前付きボリュームを定義します。
#   # 名前付きボリュームはDockerによって統合管理されており、前のモジュールで述べたように、
#   # データの永続化保存として推奨される方法です。
#
#   db_data: # データベースデータを保存するために使用するボリューム名。
#     # driver: local # ボリュームドライバを指定する例。
#     # labels:
#     #   - "com.example.description=データベース永続化ストレージ"

解説: この例は最初の例を基に構築されており、webappdatabaseという2つのサービスを示し、servicesキーの下で複数のサービス定義がどのように整理されるかを実演しています。同時に、networksvolumesのトップレベルキーを導入し、それらの役割を説明しつつ、設定がどこに配置されるべきかを示しています。これにより、より複雑なComposeファイルの完全な階層構造が確認できます。