コンテンツにスキップ

docker-compose.yml@Docker compose

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. docker-compose.ymlとは

コンテナを宣言的に定義し、コンテナのプロビジョニングを実行する。

プロビジョニングされるコンテナについては、以下のリンクを参考にせよ。


02. services

servicesとは

コンテナオーケストレーションにおける1つのコンテナを定義する。

サービス名には役割名 (appwebdb) を名付けると良い。

コンテナ名と異なり、サービス名は他のプロジェクトと重複しても良い。

docker composeコマンドの引数として指定するため、できるだけ簡潔にする。

オプション一覧は以下のリンクを参考にせよ。


args

DockerfileのARGSに展開する変数を定義する。

Dockerfileに直接的に実装することとの使い分けとして、Dockerfileの実装は簡単に変更できないが、docker-compose.ymlファイルにおける定義は変更しやすい。

そのため、使用者に変更して欲しくない変数はDockerfileに実装し、変更しても問題ない変数はこのオプションを使用する。

他に、マルチステージビルドを使用しており、全てのステージで共通した変数を展開したい場合、このオプションを使用すると展開する変数を共通化できる。

*実装例*

services:
  app:
    build:
      - PARAM=$PARAM
ARG PARAM

ENV PARAM=${PARAM}

*実装例*

# ここに実装例


build

context

指定したDockerfileのあるディレクトリをカレントディレクトリとして、dockerデーモンに送信するディレクトリを設定する。

*実装例*

services:
  app:
    build:
      context: .

dockerfile

Dockerfileまでのパスを設定する。

*実装例*

services:
  app:
    build:
      dockerfile: ./docker/app/Dockerfile

target

ビルドするステージ名を設定する。

マルチステージビルドの時に使用する。

ステージを指定しない場合、一番最後に定義したステージを使用してビルドが実行される。

*実装例*

services:
  app:
    build:
      target: develop


command

コンテナの起動時に最初に実行するコマンドを設定する。

Dockerfileを必要とせず、ベンダーが提供するイメージをそのまま使用するような場合に役立つ。

*実装例*

mysqlイメージを使用してコンテナを作成する時に、最初に文字コードを設定するコマンドを実行する。

services:
  db:
    command: |
      mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci


container_name

コンテナ名を命名する。

サービス名とは異なり、コンテナ名は他のプロジェクトと重複しないようにする必要があるため、接頭辞にプロジェクト名をつけると良い。

また、接尾辞をベンダー名とすると良い。

*実装例*

services:
  web:
    container_name: foo


depends_on

サービスを実行する順番を設定する。

*実装例*

DBサービスの起動後に、該当するコンテナを起動する。

services:
  app:
    depends_on:
      - db


env_fileenvironment

コンテナで展開する環境変数を定義する。

Dockerfile内での環境変数とは異なり、マルチステージビルドの全ステージで使用できる。

dotenv系パッケージを使用しなくてもよくなる。

*実装例*

mysqlイメージを使用した場合、DBの環境変数の設定が必要である。

DBの環境変数は、バックエンドコンテナでも必要なため、environmentキーに直接的に環境変数を設定せずに、envファイルに定義した環境変数をenvironmentキーで参照すると良い。

services:
  db:
    env_file:
      - .env
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} # root権限の実行ユーザーのパス
      MYSQL_DATABASE: ${DB_DATABASE} # DB名
      MYSQL_USER: ${DB_USER} # 一般ユーザー名
      MYSQL_PASSWORD: ${DB_PASSWORD} # 一般ユーザーのパス
# .envファイル

MYSQL_ROOT_PASSWORD=foo # root権限の実行ユーザーのパス
MYSQL_DATABASE=bar # DB名
MYSQL_USER=baz # 一般ユーザー名
MYSQL_PASSWORD=qux # 一般ユーザーのパス

mysqlイメージでは、環境変数の設定に応じて、コンテナ起動時にSQLが実行されるようになっている。

DB名の環境変数が設定されている場合は『CREATE DATABASE』、またユーザー名とパスワードが設定されている場合は『CREATE USER』と『GRANT ALL』のSQLが実行される。

Rootユーザー名は定義できず、『root』となる。


expose

他のコンテナに対してコンテナポートを開放する。

ホスト側からはアクセスできないことに注意する。

services:
  web:
    expose:
      - "80"


extra_host

コンテナに、ユーザー定義のプライベートIPアドレスと、これにマッピングされたホスト名を設定する。

マッピングは、/etc/hostsファイルに書き込まれる。

もし設定しなかった場合、サービス名またはコンテナ名がホスト名として扱われる。

services:
  web:
    extra_hosts:
      - web:162.242.195.82
$ cat /etc/hosts

127.0.0.1       127.0.0.1
::1     127.0.0.1 ip6-127.0.0.1 ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
# ユーザー定義のプライベートIPアドレスと、これにマッピングされたホスト名
162.242.195.82       web
172.23.0.3      c9bd8ace335d


healthcheck

コンテナの起動時にヘルスチェックを実行し、トラフィックを処理可能になるまでコンテナの起動完了を待機する。

トラフィックが処理可能になったらコンテナのビルドを終了し、次のコンテナのビルドを始める。

データベースのような、コンテナの起動後にトラフィックが処理可能になるまで時間がかかるツールで役立つ。

waitコマンドを実行することに相当する。

services:
  # アプリコンテナ
  app:
    depends_on:
      db:
        condition: service_healthy

  # DBコンテナ
  db:
    container_name: foo-mysql
    image: mysql:5.7
    healthcheck:
      test:
        [
          "CMD",
          "mysqladmin",
          "ping",
          "-h",
          "localhost",
          "-u",
          "root",
          "-p$MYSQL_ROOT_PASSWORD",
        ]
      # 頻度が高すぎるとMySQLの起動前にヘルスチェック処理が終わってしまうため、10秒くらいがちょうどいい
      interval: 10s
      timeout: 10s
      retries: 5


hostname

*実装例*

コンテナに割り当てられるプライベートIPアドレスに、指定したホスト名をマッピングする。

マッピングは、/etc/hostsファイルに書き込まれる。

もし設定しなかった場合、サービス名またはコンテナ名がホスト名として扱われる。

*実装例*

services:
  web:
    hostname: web
$ cat /etc/hosts

127.0.0.1       127.0.0.1
::1     127.0.0.1 ip6-127.0.0.1 ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
# プライベートIPアドレスにマッピングされたホスト名
172.18.0.3      web


image

イメージに名前をつける。

デフォルトでは、『プロジェクト名_サービス名』となる。

*実装例*

services:
  app:
    image: foo:<バージョンタグ>


include

include:
  - bar-docker-compose.yaml

services:
  app:
    image: foo:<バージョンタグ>
    depends_on:
      # 読み込んだdocker-compose内のサービス
      - bar


logging

fluentd

コンテナで作成されたログをFluentdコンテナに転送する。

ログの転送元よりも先に起動するようにしておく必要がある。

*実装例*

services:
  app:
    logging:
      driver: fluentd
      options:
        fluentd-address: 127.0.0.1:24224
        tag: app
    depends_on:
      - log_router
  log_router:
    build:
      context: .
      dockerfile: ./docker/fluentd/Dockerfile
    ports:
      - "24224:24224"


networks

dockerエンジン内の仮想ネットワーク

コンテナを接続する内/外ネットワークのエイリアスを設定する。

ネットワーク名ではなく、エイリアスを指定することに注意する。

*実装例*

networks:
  # 内/外ネットワークのエイリアスを設定する。
  - foo-network

ネットワークに接続されているコンテナはコマンドで確認できる。

# 指定したネットワークに接続するコンテナを確認する。
$ docker network inspect foo-network

[
    {
        "Name": "foo-network",

        ...

        "Containers": {
            "e681fb35e6aa5c94c85acf3522a324d7d75aad8eada13ed1779a4f8417c3fb44": {
                "Name": "<コンテナ名>",
                "EndpointID": "ef04da88901646359086eeb45aab81d2393c2f71b4266ccadc042ae49d684409",
                "MacAddress": "**:**:**:**:**:**",
                "IPv4Address": "*.*.*.*/*",
                "IPv6Address": ""
            "33632947e4210126874a7c26dce281642a6040e1acbebbdbbe8ba333c281dff8": {
                "Name": "<コンテナ名>",
                "EndpointID": "ef04da88901646359086eeb45aab81d2393c2f71b4266ccadc042ae49d684409",
                "MacAddress": "**:**:**:**:**:**",
                "IPv4Address": "*.*.*.*/*",
                "IPv6Address": ""
            }
        },

        ...

        "Labels": {
            "com.docker.compose.network": "foo-network",
            "com.docker.compose.project": "<プロジェクト名>",
            "com.docker.compose.version": "1.29.0"
        }
    }
]

注意点として、接続するネットワークは明示的に指定しなくても良い。その場合、『<プロジェクト名>_default』というネットワークが、『default』というエイリアスで作成される。

services:
  web:
    networks:
      # defaultは、明示的に指定してもしなくてもどちらでも良い。
      - default
$ docker network inspect <プロジェクト名>_default

[
    {
        "Name": "<プロジェクト名>_default",

        ...

        "Containers": {
            "e681fb35e6aa5c94c85acf3522a324d7d75aad8eada13ed1779a4f8417c3fb44": {
                "Name": "<コンテナ名>",
                "EndpointID": "ef04da88901646359086eeb45aab81d2393c2f71b4266ccadc042ae49d684409",
                "MacAddress": "**:**:**:**:**:**",
                "IPv4Address": "*.*.*.*/*",
                "IPv6Address": ""
            "33632947e4210126874a7c26dce281642a6040e1acbebbdbbe8ba333c281dff8": {
                "Name": "<コンテナ名>",
                "EndpointID": "ef04da88901646359086eeb45aab81d2393c2f71b4266ccadc042ae49d684409",
                "MacAddress": "**:**:**:**:**:**",
                "IPv4Address": "*.*.*.*/*",
                "IPv6Address": ""
            }
        },

        ...

        "Labels": {
            "com.docker.compose.network": "default",
            "com.docker.compose.project": "<プロジェクト名>",
            "com.docker.compose.version": "1.29.0"
        }
    }
]


platform

コンテナのCPUアーキテクチャ (例:Intel、AMD、ARM) を設定する。

*実装例*

services:
  app:
    platform: linux/amd64


ports

ホストとコンテナの間のポートフォワーディングを設定する。

コンテナのみポート番号を指定した場合、ホスト側のポート番号はランダムになる。

*実装例*

services:
  web:
    ports:
      - "8080:80" # <ホスト側のポート番号>:<コンテナのポート番号>


stdin_open

docker composeコマンドの裏側で実行されるdocker runコマンドで、iオプションを有効化するか否かを設定する。

*実装例*

services:
  app:
    stdin_open: "true"


tty

docker composeコマンドの裏側で実行されるdocker runコマンドで、tオプションを有効化するか否かを設定する。

疑似ターミナルを割り当てるによって、exitの後もバックグラウンドでコンテナを起動させ続けられる。

*実装例*

services:
  app:
    tty: "true"


user

docker composeコマンドの裏側で実行されるdocker runコマンドで、uオプションを有効化するか否かを設定する。

コンテナの実行ユーザーのユーザーIDとグループIDを設定する。

Root以外で実行するために使用する。

*実装例*

services:
  app:
    user: "${UID}:${GID}"


volumes (バインドマウント)

最上層とservice内で、異なるボリューム名を記述した場合、バインドマウントを定義する。

ホスト側の/Usersディレクトリをコンテナ側にマウントする。

*実装例*

services:
  app:
    volumes:
      - ./web:/var/www/foo # <ホスト側のディレクトリ>:<コンテナのディレクトリ>


volumes (ボリュームマウント)

最上層とservice内の両方に、同じボリューム名を記述した場合、ボリュームマウントを定義する。

dockerエリアにVolumeが作成され、serviceオプション内に設定したvolumesオプションでボリュームマウントを実行する。

*実装例*

MySQLコンテナのdatadirディレクトリ (/var/lib/mysql) に、dockerエリアのボリュームをマウントする。

datadirディレクトリについては、以下のリンクを参考にせよ。

service:
  db:
    volumes:
      # ボリュームマウント
      - mysql_volume:/var/lib/mysql

volumes:
  # ボリューム名
  mysql_volume:
    # localで、ホスト側のdockerエリアを指定
    driver: local

権限、バインドマウントでdatadirディレクトリにマウントしようとすると、権限エラーになってしまう。

mysqld: Can't create/write to file '/var/lib/mysql/is_writable' (Errcode: 13 - Permission denied)


変数展開

環境変数をdocker-compose.ymlファイルに展開する。変数の展開にあたり、docker-compose.ymlファイルと同じ階層にある.envファイルが自動的に読み込まれる。この展開にenv_fileオプションを使用できない。そのため、例えば.envファイル以外の名前の環境変数ファイルを変数展開のために使用できない。

*実装例*

services:
  app:
    build:
      # 出力元の値は、.envファイルに定義しなければならない。
      target: ${APP_ENV}
    image: ${APP_ENV}-foo


03. networks

networksとは

標準のネットワークを作成する。ただし定義しなくとも自動的に作成される。ネットワーク名は、指定しない場合に『<プロジェクト名>_default』になる。


name

ネットワーク名をユーザー定義名にする。

networks:
  default:
    # ユーザー定義のネットワーク名とエイリアス
    name: foo-network

注意点として、このネットワークを明示的に設定する場合は、エイリアス (default) で設定する。

services:
  web:
    networks:
      # defaultは、明示的に指定してもしなくてもどちらでも良い。
      - default
$ docker network ls

NETWORK ID       NAME                DRIVER     SCOPE
************     foo-network     bridge     local
$ docker network inspect foo-network

[
    {
        "Name": "foo-network",

        ...

        "Containers": {
            "e681fb35e6aa5c94c85acf3522a324d7d75aad8eada13ed1779a4f8417c3fb44": {
                "Name": "<コンテナ名>",
                "EndpointID": "ef04da88901646359086eeb45aab81d2393c2f71b4266ccadc042ae49d684409",
                "MacAddress": "**:**:**:**:**:**",
                "IPv4Address": "*.*.*.*/*",
                "IPv6Address": ""
            "33632947e4210126874a7c26dce281642a6040e1acbebbdbbe8ba333c281dff8": {
                "Name": "<コンテナ名>",
                "EndpointID": "ef04da88901646359086eeb45aab81d2393c2f71b4266ccadc042ae49d684409",
                "MacAddress": "**:**:**:**:**:**",
                "IPv4Address": "*.*.*.*/*",
                "IPv6Address": ""
            }
        },

        ...

        "Labels": {
            "com.docker.compose.network": "foo-network",
            "com.docker.compose.project": "<プロジェクト名>",
            "com.docker.compose.version": "1.29.0"
        }
    }
]


external

docker-compose_external

異なるdocker-compose.ymlファイルから相互に通信できるネットワークを作成する。作成されるネットワーク名は、<プロジェクト名>_<外部ネットワーク名>になる。

*実装例*

バックエンドとフロントエンドが異なるdocker-compose.ymlファイルで管理されている。フロントエンドコンテナとバックエンドコンテナの間で相互に通信できるように、ネットワークを公開する。

# バックエンドのDocker-compose
services:
  app:
    container_name: backend-container
    networks:
      # 接続したい外部ネットワーク名
      - shared-network

---
networks:
  # 公開したい外部ネットワーク名
  foo:
    external: "true"

フロントエンドコンテナにて、同じ名前の外部ネットワークを作成し、公開する。

# フロントエンドのDocker-compose
services:
  app:
    container_name: frontend-container
    networks:
      # 接続したい外部ネットワーク名
      - shared-network

---
networks:
  # 公開したい外部ネットワーク名
  foo:
    external: "true"


04. プラグイン

Volumeプラグイン

▼ NFSストレージ

NFSプラグインを使用することにより、永続データを/var/lib/docker/volumesディレクトリではなく、NFSストレージに保管する。

*実装例*

以下にdocker-composeを使用した場合を示す。

version: "3.9"

services:
  app:
    build: ...
    ports: ...
    depends_on: ...
    volumes:
      - example:/data # 下方のオプションが適用される。

volumes:
  example:
    driver_opts: # NFSプラグインを使用して、NFSストレージに保管。
      type: "nfs"
      o: "addr=10.40.0.199,nolock,soft,rw"
      device: ":/nfs/example"


05. イメージ別プラクティス

mysqlイメージ

▼ ビルド時にSQL実行

mysqlコンテナにはdocker-entrypoint-initdb.dディレクトリがある。

このディレクトリ配下に配置されたsqlファイルやbashプロセスは、mysqlコンテナのビルド時にdocker-entrypoint.shファイルによって実行される。

そのため、バインドマウントを使用してこのディレクトリ配下にファイルを配置することにより、初期データの投入や複数DBの作成を実現できる。

具体的な実行タイミングについては、以下のリンクを参考にせよ。

*実装例*

mysqlコンテナに、PHPUnitの実行時のみ使用するDBを追加する。以下のような、docker-compose.ymlファイルを作成する。

version: "3.9"

services:
  db:
    container_name: foo-mysql
    hostname: foo-mysql
    image: mysql:5.7
    ports:
      - "3307:3306"
    volumes:
      - mysql_volume:/var/lib/mysql
      # docker-entrypoint-initdb.dディレクトリにバインドマウントを実行する。
      - ./infra/docker/mysql/init:/docker-entrypoint-initdb.d
    environment:
      MYSQL_ROOT_PASSWORD: foo
      MYSQL_DATABASE: foo
      MYSQL_USER: foo
      MYSQL_PASSWORD: foo
      TZ: "Asia/Tokyo"
    command: |
      mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    networks:
      - default

volumes:
  mysql_volume:

また、docker-entrypoint-initdb.dディレクトリ配下に配置するファイルとして、以下のsqlファイルを作成する。このファイルでは、testというDBを作成するためのSQLを実装する。

-- /infra/docker/mysql/initにSQLファイルを配置する。
CREATE DATABASE IF NOT EXISTS `test` COLLATE 'utf8mb4_general_ci' CHARACTER SET 'utf8mb4';
GRANT ALL ON *.* TO 'foo'@'%' ;

PHPUnitで接続するDBを指定する方法については、以下のリンクを参考にせよ。