Docker 入門

Docker Compose コアチュートリアル:サービス (Services)・ネットワーク (Networks)・ボリューム (Volumes) の設定

Docker Composeを使用すると、複数のコンテナで構成されるDockerアプリケーションを定義し、実行することができます。Composeの中核となるのは、アプリケーションのサービス(Services)、ネットワーク(Networks)、およびボリューム(Volumes)を設定するためのYAMLファイル(通常は docker-compose.yml という名前)です。このファイルは設計図のような役割を果たし、アプリケーションの各コンポーネントがどのように相互作用し、データをどのように永続化するかを記述します。

1. サービス (Services) の定義

Docker Composeにおいて、サービス(Service)はアプリケーションの特定の部分を実行する独立したコンテナを表します。例えば、Webアプリケーションの場合、Webサーバー(NginxやApacheなど)用のサービス、アプリケーションコード(Python FlaskアプリケーションやNode.jsアプリケーションなど)用のサービス、そしてデータベース(PostgreSQLやMongoDBなど)用のサービスが存在する場合があります。各サービスは、docker-compose.yml ファイルの services キーの配下で定義されます。

各サービス定義には、docker run コマンドラインで指定する設定オプションと同様のものが含まれます。コアとなるオプションには、image(イメージ)、build(ビルド)、ports(ポート)、volumes(ボリューム)、および networks(ネットワーク)があります。

1.1 基本的なサービス定義

Nginx Webサーバーで構成されるシンプルなWebアプリケーションを見てみましょう:

version: '3.8' # Composeファイルフォーマットのバージョンを指定

services:
  web: # サービスの名称
    image: nginx:latest # このサービスが使用するDockerイメージ
    ports:
      - "80:80" # ホストマシンの80番ポートをコンテナの80番ポートにマッピング

この例では:

  • version: '3.8': Composeファイルのフォーマットバージョンを指定します。3.8 のようなモダンなバージョンを使用することで、最新の機能やベストプラクティスを利用できます。
  • services:: すべてのアプリケーションサービスを定義するためのトップレベルのキーです。
  • web:: 最初のサービスの名称です。Composeは内部ネットワークでこの名前を使用してDNS解決を行い、サービスが名前ベースで相互通信できるようにします。
  • image: nginx:latest: Docker Hubから nginx:latest Dockerイメージを使用するようComposeに指示します。ローカルでイメージが見つからない場合、Composeは自動的にプル(取得)します。
  • ports:: ホストマシンとコンテナ間でポートをマッピングします。"80:80" は、ホストマシンの80番ポートをコンテナの80番ポートにマッピングし、ホストマシンのネットワークインターフェース経由でNginxサーバーにアクセスできるようにします。

1.2 カスタムイメージを使用したサービス定義

通常、独自のアプリケーションコード用にカスタムDockerイメージをビルドする必要があります。build オプションは、Dockerfile の場所を指定するために使用します。

version: '3.8'

services:
  app:
    build: ./app # Dockerfileが含まれるディレクトリパスを指定
    ports:
      - "5000:5000"
    command: python app.py # Dockerfile内のデフォルトコマンドを上書き

  web:
    image: nginx:latest
    ports:
      - "80:80"
    depends_on: # 'app'サービスが'web'よりも前に起動することを保証('app'がヘルス状態になるまでは待機しません)
      - app

ここでは、app サービスは ./app ディレクトリ内にある Dockerfile からビルドされます。command オプションは、Dockerfile で指定されたこのサービスのデフォルトコマンドを上書きし、python app.py が確実に実行されるようにします。depends_on キーワードはサービス間の依存関係を表現し、web サービスが app サービスより後に起動するべきであることを示します。ただし、depends_on は起動順序を保証するだけで、依存するサービスが「準備完了」または「ヘルス状態」になるのを待機するわけではありません。あくまでコンテナが起動するのを待つのみです。

1.3 環境変数と再起動ポリシー

環境変数をサービスに注入したり、コンテナが終了した際の挙動を定義したりすることができます。

version: '3.8'

services:
  database:
    image: postgres:13
    environment: # コンテナ内部で環境変数を設定
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    restart: always # コンテナが停止した場合、常に再起動する

  backend:
    build: ./backend
    environment:
      DATABASE_HOST: database # 'database'というサービス名を参照
      DATABASE_PORT: 5432
    ports:
      - "8080:8080"
    restart: on-failure # コンテナが非ゼロのステータスコード(エラー)で終了した場合のみ再起動する

この設定において、database サービスは postgres:13 を使用し、データベース設定に必要な重要な環境変数を設定しています。restart: always ポリシーは、PostgreSQLコンテナが何らかの理由で停止した場合に、Composeが常に再起動を試みることを保証します。backend サービスはローカルの ./backend ディレクトリからビルドされ、環境変数を使用して database サービスに接続し、コンテナが予期せぬエラーで終了した場合にのみ再起動する restart: on-failure を使用しています。

2. ネットワーク (Networks) の定義

Docker Composeは、アプリケーション用のデフォルトのブリッジネットワーク(Bridge Network)を自動的に作成し、すべてのサービスがサービス名を使用して相互に通信できるようにします。しかし、カスタムネットワークを定義してアプリケーションのトラフィックを分離したり、外部ネットワークに接続したりすることもできます。カスタムネットワークを定義することで、サービス間の通信をより細かく制御し、セキュリティを向上させることができます。

2.1 暗黙的なデフォルトネットワーク

ネットワークを明示的に定義しなかった場合、Composeはアプリケーション用のデフォルトネットワークを作成します。すべてのサービスは自動的にこのネットワークに接続されます。

以前の例を振り返ってみましょう:

version: '3.8'

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

  app:
    build: ./app
    ports:
      - "5000:5000"

このケースでは、Composeはデフォルトネットワーク(例えば、プロジェクトのディレクトリが myproject の場合、ネットワーク名は myproject_default になります)を作成します。web サービスと app サービスは両方ともこのネットワークにアタッチされ、web はホスト名 app とポート5000を経由して app にアクセスできるようになります(例:http://app:5000 へのリクエスト)。

2.2 明示的なカスタムネットワーク

トップレベルの networks キーの下でカスタムネットワークを定義し、各サービス定義内の networks オプションを使用して、サービスをこれらのネットワークに割り当てることができます。

version: '3.8'

services:
  frontend:
    image: frontend-app:latest
    ports:
      - "80:80"
    networks:
      - frontend-network # 'frontend-network'に接続

  backend:
    image: backend-app:latest
    networks:
      - frontend-network # 'frontend-network'に接続
      - backend-network # 'backend-network'に接続

  database:
    image: postgres:13
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    networks:
      - backend-network # 'backend-network'に接続

networks: # ネットワークを定義するトップレベルキー
  frontend-network: # 'frontend-network'という名前のカスタムネットワーク
    driver: bridge # ネットワークドライバを指定、デフォルトは'bridge'
  backend-network: # 'backend-network'という名前のカスタムネットワーク
    driver: bridge

この構成では:

  • frontend サービスは frontend-network のみに接続されます。
  • backend サービスは frontend-networkbackend-network の2つのネットワークに接続されます。これにより、フロントエンドからのリクエストを受信しつつ、データベースに接続することができます。
  • database サービスは backend-network のみに接続されます。

この分離により、frontend サービスはデータベースと同じネットワークを共有していないため、直接通信することはできません。これによりセキュリティが大幅に強化され、通信パスが明確に分離されます。

2.3 外部ネットワーク

また、docker network create を使用して手動で作成したネットワークなど、docker-compose.yml ファイルの外部で作成されたネットワークにComposeアプリケーションを接続することもできます。

version: '3.8'

services:
  my-app:
    image: my-custom-app:latest
    networks:
      - existing-shared-network # 既存の外部ネットワークに接続

networks:
  existing-shared-network:
    external: true # これが外部ネットワークであることを宣言
    name: my-global-app-network # 外部ネットワークの実際の名前を指定

ここでは、my-appmy-global-app-network という名前の既存ネットワークに接続されます。external: true フラグは、ネットワークを作成するのではなく、すでに存在するネットワークを探すようにComposeに指示します。これは、Composeアプリケーションを共有ネットワーク上で実行されている他のDockerコンテナやサービスと統合する際に非常に有用です。

3. ボリューム (Volumes) の定義

ボリュームは、Dockerコンテナが生成・使用するデータを永続化するための推奨メカニズムです。これにより、データはコンテナのライフサイクルを超えて存続することができます。Composeでは、Dockerによって管理される名前付きボリューム(Named Volumes)を定義し、それらをサービスにマウントすることができます。

3.1 名前付きボリューム (Named Volumes)

名前付きボリュームはトップレベルの volumes キーの配下で定義され、その後、各サービスから参照することができます。

version: '3.8'

services:
  database:
    image: postgres:13
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - db-data:/var/lib/postgresql/data # 名前付きボリューム'db-data'をコンテナ内部にマウント
    networks:
      - backend-network

  backend:
    build: ./backend
    ports:
      - "8080:8080"
    networks:
      - backend-network

volumes: # ボリュームを定義するトップレベルキー
  db-data: # 名前付きボリュームの名称

この例では:

  • db-data: 名前付きボリュームを定義しています。Composeはこのボリュームを作成し、管理します。
  • database サービスは db-data:/var/lib/postgresql/data を使用しています。これは、db-data ボリュームをPostgreSQLコンテナ内の /var/lib/postgresql/data パス(PostgreSQLがデータを保存するデフォルトディレクトリ)にマウントします。これにより、database コンテナが削除されたとしても、データは db-data ボリューム内に安全に保持されます。このComposeファイルを使用して新しい database コンテナを起動すると、既存の db-data ボリュームが再利用され、以前に保存されたデータが復元されます。

3.2 ドライバオプション付きのボリューム

ボリュームに driver(ドライバ)を指定することで、ネットワークファイルシステム(NFS)やクラウドストレージプラグインなど、異なるストレージバックエンドを使用することができます。

version: '3.8'

services:
  web:
    image: nginx:latest
    volumes:
      - web-content:/usr/share/nginx/html # 'web-content'ボリュームをマウント
    ports:
      - "80:80"

volumes:
  web-content:
    driver: local # 'local'ローカルドライバ(デフォルト)の使用を明示的に定義
    driver_opts: # このドライバに対する具体的なオプション
      type: "nfs"
      o: "addr=192.168.1.100,rw" # NFSオプションの例
      device: ":/path/to/nfs/share"

ここでは、web-content ボリュームは driver: localdriver_opts を設定してNFSマウントをエミュレートしています。これはより高度なユースケースであり、通常は共有ストレージが必要とされる本番環境で見られます。

3.3 ホストマウント / バインドマウント (Bind Mounts)

本番環境では通常、データの永続化には名前付きボリュームが推奨されますが、開発環境ではバインドマウントが非常に有用です。バインドマウントを使用すると、ホストマシンのローカルディレクトリをコンテナに直接マウントできます。ホストマシンで行った変更がイメージを再ビルドすることなく即座にコンテナ内に反映されるため、コード開発時に特に便利です。

version: '3.8'

services:
  app:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - ./app:/usr/src/app # ローカルの'./app'ディレクトリをコンテナの'/usr/src/app'にマウント
    environment:
      PYTHONUNBUFFERED: 1 # Python開発環境のサンプル環境変数

この設定では、ローカルの ./app ディレクトリにあるコードがコンテナの /usr/src/app にマウントされます。つまり、ホストマシンの ./app でファイルに加えた変更は、すべて稼働中の app コンテナ内部で即座に反映され、高速な開発イテレーションが実現します。

4. 総合的な実践ケース

サービス、ネットワーク、およびボリュームを組み合わせて、より完全なアプリケーションを構築してみましょう。ここでは、Node.js API、Reactフロントエンド、およびMongoDBデータベースを使用するシンプルなブログシステムを例にします。

4.1 プロジェクトのディレクトリ構造

.
├── docker-compose.yml
├── frontend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
│       └── App.js
├── backend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
│       └── app.js
└── data/ # 潜在的なローカルデータベースファイル用に確保されたプレースホルダー(ただし、本例のMongoDBはボリュームを使用します)

4.2 docker-compose.yml の設定解析

version: '3.8'

services:
  frontend:
    build: ./frontend # ./frontend/Dockerfileからフロントエンドイメージをビルド
    ports:
      - "3000:3000" # ホストマシンのポート3000をコンテナのポート3000にマッピング
    volumes:
      - ./frontend:/app # 開発用のバインドマウント:ローカルコードをコンテナの/appにマウント
      - /app/node_modules # 匿名ボリューム:ホストマシンのnode_modulesをコンテナから隠すため
    environment:
      REACT_APP_API_URL: http://localhost:8080 # ブラウザからアクセス可能なバックエンドAPIを指す
    networks:
      - app-network # メインのアプリケーションネットワークに接続
    depends_on:
      - backend # フロントエンドはAPI呼び出しのためにバックエンドに依存(起動順序)

  backend:
    build: ./backend # ./backend/Dockerfileからバックエンドイメージをビルド
    ports:
      - "8080:8080" # ホストマシンのポート8080をコンテナのポート8080にマッピング
    volumes:
      - ./backend:/app # 開発用のバインドマウント:ローカルコードをコンテナの/appにマウント
      - /app/node_modules # 匿名ボリューム:ホストマシンのnode_modulesをコンテナから隠すため
    environment:
      NODE_ENV: development
      MONGO_URI: mongodb://database:27017/blogdb # 'app-network'内の'database'サービスに接続
    networks:
      - app-network # メインのアプリケーションネットワークに接続
    depends_on:
      - database # バックエンドはデータベースに依存

  database:
    image: mongo:latest # 公式のMongoDBイメージを使用
    ports:
      - "27017:27017" # 潜在的な外部アクセス(例:MongoDB Compassなど)のためにMongoDBを公開
    volumes:
      - mongo-data:/data/db # MongoDBデータの永続化に使用する名前付きボリューム
    networks:
      - app-network # メインのアプリケーションネットワークに接続

networks:
  app-network: # アプリケーションコンポーネント用のカスタムブリッジネットワーク
    driver: bridge

volumes:
  mongo-data: # MongoDBデータの永続化に使用する名前付きボリューム

4.3 付属の Dockerfile サンプル

frontend/Dockerfile

# Node.jsベースイメージを使用してビルドと実行を行う
FROM node:18-alpine

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

# 依存関係をインストールするためにpackage.jsonとpackage-lock.jsonをコピー
COPY package*.json ./

# 依存関係のインストール
RUN npm install

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

# アプリケーションが実行されるポートを公開
EXPOSE 3000

# アプリケーションを実行するコマンド
CMD ["npm", "start"]

backend/Dockerfile

# Node.jsベースイメージを使用
FROM node:18-alpine

# 作業ディレクトリを設定
WORKDIR /app

# package.jsonとpackage-lock.jsonをコピー
COPY package*.json ./

# 依存関係のインストール
RUN npm install

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

# APIが実行されるポートを公開
EXPOSE 8080

# アプリケーションを実行するコマンド
CMD ["npm", "start"]

4.4 ケースの原理詳細解説

サービス (Services):

  • frontend: Reactアプリをビルドします。ホストマシンのポート3000をコンテナのポート3000にバインドします。リアルタイム開発のために ./frontend ディレクトリのバインドマウントを使用し、コンテナ内の node_modules ディレクトリがホストマシンの node_modules によって上書きされないように匿名ボリューム(/app/node_modules)を使用しています(これらは異なるOSアーキテクチャを持つ可能性があります)。
  • backend: Node.js APIアプリをビルドします。ホストマシンのポート8080をコンテナのポート8080にバインドし、同様にバインドマウントと匿名ボリュームを使用しています。database サービスに接続するために MONGO_URI を設定しています。
  • database: mongo:latest イメージを使用します。ポート27017を公開し、重要なポイントとして、mongo-data という名前付きボリュームを使用してデータを /data/db パスに永続化しています。

ネットワーク (Networks):

  • app-network: 単一のカスタムブリッジネットワークを定義しています。3つのすべてのサービス(frontendbackenddatabase)がこのネットワークに接続されます。これにより、サービス名を使用して互いに通信できるようになります(例:backendmongodb://database:27017 を使用して database に接続します)。

ボリューム (Volumes):

  • mongo-data: volumes セクションで明示的に定義された名前付きボリュームです。このボリュームはDockerによって管理され、database コンテナが停止、削除、または再作成されたとしてもMongoDBのデータが保持されることを保証します。
  • ./frontend:/app および ./backend:/app: これらは開発用のバインドマウントです。ローカルプロジェクトのコードとコンテナを同期させ、イメージの再ビルドなしでリアルタイムなコード変更を可能にします。
  • /app/node_modules: これらは匿名ボリューム(Anonymous Volumes)です。ホストマシンの node_modules ディレクトリがコンテナ内の node_modules に干渉するのを防ぐために使用されます。Dockerはコンテナ内の /app/node_modules に匿名ボリュームを作成し、その特定のパスにおいてはバインドマウントよりも匿名ボリュームの優先順位が高くなります。これはソースコードにバインドマウントを使用する場合、Node.jsプロジェクトにおいて極めて一般的なパターンです。

この包括的な docker-compose.yml ファイルは、完全な複数コンテナアプリケーションを定義しており、各コンポーネントがどのようにビルド、実行、通信、およびデータの永続化を行うべきかを明確に規定しています。