CLOVER🍀

That was when it all began.

Docker Composeの構成ファイル内で環境変数を参照する

これは、なにをしたくて書いたもの?

Docker Composeの構成ファイル内で(環境)変数を参照できるのはなんとなく知っていましたが、ちゃんと試したことがなかったので
自分でも確認しておきたいなということで。

環境

今回の環境は、こちら。

$ docker version
Client: Docker Engine - Community
 Version:           23.0.1
 API version:       1.42
 Go version:        go1.19.5
 Git commit:        a5ee5b1
 Built:             Thu Feb  9 19:47:01 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          23.0.1
  API version:      1.42 (minimum version 1.12)
  Go version:       go1.19.5
  Git commit:       bc3805a
  Built:            Thu Feb  9 19:47:01 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.18
  GitCommit:        2456e983eb9e37e47538f59ea18f2043c9a73640
 runc:
  Version:          1.1.4
  GitCommit:        v1.1.4-0-g5fd4c4d
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0


$ docker compose version
Docker Compose version v2.16.0

Docker Composeの構成ファイルで変数を参照する

こちらのドキュメントで、簡単に触れられていますが

Ways to set environment variables in Compose | Docker Documentation

Docker Composeの構成ファイルのリファレンスの、Interpolationを参照するのがよいでしょうね。

Compose file specification / Interpolation

bashと似た${...}という構文で参照するようです。変数、変数と書いていますが、実体としては環境変数ですね。

ちょっと見てみましょう。

  • $VARIABLEと${VARIABLE}の両方の書き方をサポート
  • シェル構文を使ったもの
    • ${VARIABLE:-default} … 変数VARIABLEが設定されていないまたは空の場合は、デフォルト値としてdefaultを使用する
    • ${VARIABLE-default} … 変数VARIABLEが設定されていない場合にのみ、デフォルトとしてdefaultを使用する
    • ${VARIABLE:?err} … 変数VARIABLEが設定されていないまたは空の場合は、エラーメッセージとしてerrを出力して終了する
    • ${VARIABLE?err} … 変数VARIABLEが設定されていない場合は、エラーメッセージとしてerrを出力して終了する

ネストも可能なようです。

  • ${VARIABLE:-${FOO}}
  • ${VARIABLE?$FOO}
  • ${VARIABLE:-${FOO:-default}}

${VARIABLE/foo/bar}といった拡張はサポートしていないそうです。

$自体を文字として使いたい場合は、$$とするようです。

You can use a $$ (double-dollar sign) when your configuration needs a literal dollar sign.

また、変数が定義されておらず、デフォルト値もない場合は空の文字列を設定し、ユーザーに警告するようです。

If the Compose implementation can’t resolve a substituted variable and no default value is defined, it MUST warn the user and substitute the variable with an empty string.

というわけで、ちょっと試してみましょう。

お題

MinIOのDockerイメージを使う時に、タグの指定とコンソールポートを変数で指定するようにしてみましょう。

minio/minio

雛形

まずは、以下からスタートしましょう。

compose.yaml

services:
  minio:
    image: minio/minio:RELEASE.2023-02-17T17-52-43Z
    ports:
      - 9000:9000
      - 9001:9001
    command: server /data --console-address ":9001"

起動。

$ docker compose up

この時のバージョンやコンソールポートの情報。

xxxxx-minio-1  | MinIO Object Storage Server
xxxxx-minio-1  | Copyright: 2015-2023 MinIO, Inc.
xxxxx-minio-1  | License: GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>
xxxxx-minio-1  | Version: RELEASE.2023-02-17T17-52-43Z (go1.19.6 linux/amd64)
xxxxx-minio-1  |
xxxxx-minio-1  | Status:         1 Online, 0 Offline.
xxxxx-minio-1  | API: http://172.19.0.2:9000  http://127.0.0.1:9000
xxxxx-minio-1  | Console: http://172.19.0.2:9001 http://127.0.0.1:9001

変数を参照する

では、変数を参照するようにしてみましょう。

以下のように変更。

compose.yaml

services:
  minio:
    image: minio/minio:${MINIO_TAG}
    ports:
      - 9000:9000
      - ${MINIO_CONSOLE_PORT}:${MINIO_CONSOLE_PORT}
    command: server /data --console-address ":${MINIO_CONSOLE_PORT}"

タグをMINIO_TAGで、コンソールポートをMINIO_CONSOLE_PORTで指定するようにしています。

このまま変数は指定せずに起動すると

$ docker compose up

起動はするものの、ドキュメント通り変数が解決できないので、空の文字列として扱うことが出力されます。

WARN[0000] The "MINIO_TAG" variable is not set. Defaulting to a blank string.
WARN[0000] The "MINIO_CONSOLE_PORT" variable is not set. Defaulting to a blank string.
WARN[0000] The "MINIO_CONSOLE_PORT" variable is not set. Defaulting to a blank string.
WARN[0000] The "MINIO_CONSOLE_PORT" variable is not set. Defaulting to a blank string.

今回の場合は、portsも変数化したので解決できずに起動に失敗します。

* error decoding 'Ports': No port specified: :<empty>

というわけで、変数を与えて起動してみます。
※exportしてもよいです

$ MINIO_TAG=RELEASE.2023-02-10T18-48-39Z MINIO_CONSOLE_PORT=9003 docker compose up

使用するタグはひとつ古いバージョンにして、ポートは9003にしてみました。

xxxxx-minio-1  | MinIO Object Storage Server
xxxxx-minio-1  | Copyright: 2015-2023 MinIO, Inc.
xxxxx-minio-1  | License: GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>
xxxxx-minio-1  | Version: RELEASE.2023-02-10T18-48-39Z (go1.19.4 linux/amd64)
xxxxx-minio-1  |
xxxxx-minio-1  | Status:         1 Online, 0 Offline.
xxxxx-minio-1  | API: http://172.19.0.2:9000  http://127.0.0.1:9000
xxxxx-minio-1  | Console: http://172.19.0.2:9003 http://127.0.0.1:9003

反映されているようです。

ローカルにバインドされているポートも変わっていますね。

$ MINIO_TAG=RELEASE.2023-02-10T18-48-39Z MINIO_CONSOLE_PORT=9003 docker compose ps
NAME                                   IMAGE                                      COMMAND                  SERVICE             CREATED             STATUS              PORTS
xxxxx-minio-1   minio/minio:RELEASE.2023-02-10T18-48-39Z   "/usr/bin/docker-ent…"   minio               2 minutes ago       Up 2 minutes        0.0.0.0:9000->9000/tcp, :::9000->9000/tcp, 0.0.0.0:9003->9003/tcp, :::9003->9003/tcp

この時、変数展開した結果としてどのように解釈されるかは、docker compose convertで見れるようです。

$ MINIO_TAG=RELEASE.2023-02-10T18-48-39Z MINIO_CONSOLE_PORT=9003 docker compose convert
name: xxxxx
services:
  minio:
    command:
    - server
    - /data
    - --console-address
    - :9003
    image: minio/minio:RELEASE.2023-02-10T18-48-39Z
    networks:
      default: null
    ports:
    - mode: ingress
      target: 9000
      published: "9000"
      protocol: tcp
    - mode: ingress
      target: 9003
      published: "9003"
      protocol: tcp
networks:
  default:
    name: xxxxx_default_default

変数を指定していない場合にエラーにする

次は、変数を指定していない場合にエラーにしてみましょう。

compose.yaml

services:
  minio:
    image: minio/minio:${MINIO_TAG?missing tag}
    ports:
      - 9000:9000
      - ${MINIO_CONSOLE_PORT?missing console port}:${MINIO_CONSOLE_PORT?missing console port}
    command: server /data --console-address ":${MINIO_CONSOLE_PORT?missing console port}"

特になにも指定せずに起動すると

$ docker compose up

MINIO_TAGがないとエラーになりました。

invalid interpolation format for services.minio.image.
You may need to escape any $ with another $.
required variable MINIO_TAG is missing a value: missing tag

MINIO_TAGだけ指定すると、次はMINIO_CONSOLE_PORTがないと言われます。

$ MINIO_TAG=RELEASE.2023-02-10T18-48-39Z docker compose up
invalid interpolation format for services.minio.ports.[].
You may need to escape any $ with another $.
required variable MINIO_CONSOLE_PORT is missing a value: missing console port

デフォルト値を指定する

最後に、デフォルト値を指定してみます。

compose.yaml

services:
  minio:
    image: minio/minio:${MINIO_TAG:-RELEASE.2023-02-17T17-52-43Z}
    ports:
      - 9000:9000
      - ${MINIO_CONSOLE_PORT:-9001}:${MINIO_CONSOLE_PORT:-9001}
    command: server /data --console-address ":${MINIO_CONSOLE_PORT:-9001}"

特になにも指定せずに起動すると

$ docker compose up

デフォルト値として指定した値が使われます。

xxxxx-minio-1  | MinIO Object Storage Server
xxxxx-minio-1  | Copyright: 2015-2023 MinIO, Inc.
xxxxx-minio-1  | License: GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>
xxxxx-minio-1  | Version: RELEASE.2023-02-17T17-52-43Z (go1.19.6 linux/amd64)
xxxxx-minio-1  |
xxxxx-minio-1  | Status:         1 Online, 0 Offline.
xxxxx-minio-1  | API: http://172.19.0.2:9000  http://127.0.0.1:9000
xxxxx-minio-1  | Console: http://172.19.0.2:9001 http://127.0.0.1:9001

変数を指定すると

$ MINIO_TAG=RELEASE.2023-02-10T18-48-39Z MINIO_CONSOLE_PORT=9003 docker compose up

その値が使われます。

xxxxx-minio-1  | MinIO Object Storage Server
xxxxx-minio-1  | Copyright: 2015-2023 MinIO, Inc.
xxxxx-minio-1  | License: GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>
xxxxx-minio-1  | Version: RELEASE.2023-02-10T18-48-39Z (go1.19.4 linux/amd64)
xxxxx-minio-1  |
xxxxx-minio-1  | Status:         1 Online, 0 Offline.
xxxxx-minio-1  | API: http://172.19.0.2:9000  http://127.0.0.1:9000
xxxxx-minio-1  | Console: http://172.19.0.2:9003 http://127.0.0.1:9003

docker compose convertの結果。

変数を指定しない場合。

$ docker compose convert
name: xxxxx_default
services:
  minio:
    command:
    - server
    - /data
    - --console-address
    - :9001
    image: minio/minio:RELEASE.2023-02-17T17-52-43Z
    networks:
      default: null
    ports:
    - mode: ingress
      target: 9000
      published: "9000"
      protocol: tcp
    - mode: ingress
      target: 9001
      published: "9001"
      protocol: tcp
networks:
  default:
    name: xxxxx_default

変数を指定した場合。

$ MINIO_TAG=RELEASE.2023-02-10T18-48-39Z MINIO_CONSOLE_PORT=9003 docker compose convert
name: xxxxx
services:
  minio:
    command:
    - server
    - /data
    - --console-address
    - :9003
    image: minio/minio:RELEASE.2023-02-10T18-48-39Z
    networks:
      default: null
    ports:
    - mode: ingress
      target: 9000
      published: "9000"
      protocol: tcp
    - mode: ingress
      target: 9003
      published: "9003"
      protocol: tcp
networks:
  default:
    name: xxxxx_default

こんなところでしょうか。

もう少し例を

先日、こんなエントリーを書きました。

Docker Composeで、コンテナの起動時に初期処理をしたい - CLOVER🍀

ここで指定していたクレデンシャルを、変数で設定するようにしてみましょう。

こんな感じで。

compose.yaml

services:
  minio:
    image: minio/minio:RELEASE.2023-02-17T17-52-43Z
    ports:
      - 9000:9000
      - 9001:9001
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: ${MY_MINIO_ROOT_USER:-defaultminioadmin}
      MINIO_ROOT_PASSWORD: ${MY_MINIO_ROOT_PASSWORD:-defaultminioadmin}
  minio-setup:
    image: minio/mc:RELEASE.2023-02-16T19-20-11Z
    restart: "no"
    depends_on:
      - minio
    entrypoint: []
    command: >-
      bash -c 'sleep 3 \
               && mc alias set myminio http://minio:9000 ${MY_MINIO_ROOT_USER:-defaultminioadmin} ${MY_MINIO_ROOT_PASSWORD:-defaultminioadmin} \
               && mc mb myminio/my-bucket  \
               && echo "minio setup complete"'

なにも指定しないとdefaultminioadminという値をクレデンシャルとして使い、設定する場合はMY_MINIO_ROOT_USERと
MY_MINIO_ROOT_PASSWORDという変数を使うことにします。

    environment:
      MINIO_ROOT_USER: ${MY_MINIO_ROOT_USER:-defaultminioadmin}
      MINIO_ROOT_PASSWORD: ${MY_MINIO_ROOT_PASSWORD:-defaultminioadmin}

起動時にバケットも作成するためのコンテナも定義していますが、こちらはcommand内で参照するようにしています。

    entrypoint: []
    command: >-
      bash -c 'sleep 3 \
               && mc alias set myminio http://minio:9000 ${MY_MINIO_ROOT_USER:-defaultminioadmin} ${MY_MINIO_ROOT_PASSWORD:-defaultminioadmin} \
               && mc mb myminio/my-bucket  \
               && echo "minio setup complete"'

まずは変数に設定せずに起動。

$ docker compose up

確認。

$ export AWS_ACCESS_KEY_ID=defaultminioadmin
$ export AWS_SECRET_ACCESS_KEY=defaultminioadmin
$ export AWS_DEFAULT_REGION=us-east-1
$ aws s3 ls --endpoint-url http://localhost:9000
2023-02-19 21:26:42 my-bucket

OKですね。

なお、MinIOの本来のデフォルトのクレデンシャルはminioadminなので、この値を指定するとクレデンシャルでエラーになります。

$ export AWS_ACCESS_KEY_ID=minioadmin
$ export AWS_SECRET_ACCESS_KEY=minioadmin
$ export AWS_DEFAULT_REGION=us-east-1
$ aws s3 ls --endpoint-url http://localhost:9000

An error occurred (InvalidAccessKeyId) when calling the ListBuckets operation: The Access Key Id you provided does not exist in our records.

docker compose convertしてみると、commandの中を含めて展開されているのがわかります。

$ docker compose convert
name: xxxxx
services:
  minio:
    command:
    - server
    - /data
    - --console-address
    - :9001
    environment:
      MINIO_ROOT_PASSWORD: defaultminioadmin
      MINIO_ROOT_USER: defaultminioadmin
    image: minio/minio:RELEASE.2023-02-17T17-52-43Z
    networks:
      default: null
    ports:
    - mode: ingress
      target: 9000
      published: "9000"
      protocol: tcp
    - mode: ingress
      target: 9001
      published: "9001"
      protocol: tcp
  minio-setup:
    command:
    - bash
    - -c
    - |-
      sleep 3 \
               && mc alias set myminio http://minio:9000 defaultminioadmin defaultminioadmin \
               && mc mb myminio/my-bucket  \
               && echo "minio setup complete"
    depends_on:
      minio:
        condition: service_started
    entrypoint: []
    image: minio/mc:RELEASE.2023-02-16T19-20-11Z
    networks:
      default: null
    restart: "no"
networks:
  default:
    name: xxxxx_default

次は、変数を指定して起動してみます。

$ MY_MINIO_ROOT_USER=myminioadmin MY_MINIO_ROOT_PASSWORD=myminioadmin docker compose up

確認。

$ export AWS_ACCESS_KEY_ID=myminioadmin
$ export AWS_SECRET_ACCESS_KEY=myminioadmin
$ export AWS_DEFAULT_REGION=us-east-1
$ aws s3 ls --endpoint-url http://localhost:9000
2023-02-19 21:31:02 my-bucket

こちらもOKですね。

docker compose convertの結果。

$ MY_MINIO_ROOT_USER=myminioadmin MY_MINIO_ROOT_PASSWORD=myminioadmin docker compose convert
name: xxxxx
services:
  minio:
    command:
    - server
    - /data
    - --console-address
    - :9001
    environment:
      MINIO_ROOT_PASSWORD: myminioadmin
      MINIO_ROOT_USER: myminioadmin
    image: minio/minio:RELEASE.2023-02-17T17-52-43Z
    networks:
      default: null
    ports:
    - mode: ingress
      target: 9000
      published: "9000"
      protocol: tcp
    - mode: ingress
      target: 9001
      published: "9001"
      protocol: tcp
  minio-setup:
    command:
    - bash
    - -c
    - |-
      sleep 3 \
               && mc alias set myminio http://minio:9000 myminioadmin myminioadmin \
               && mc mb myminio/my-bucket  \
               && echo "minio setup complete"
    depends_on:
      minio:
        condition: service_started
    entrypoint: []
    image: minio/mc:RELEASE.2023-02-16T19-20-11Z
    networks:
      default: null
    restart: "no"
networks:
  default:
    name: xxxxx_default

こんなところでしょうか。

まとめ

Docker Composeの構成ファイル内で、環境変数を参照してみました。

構文やデフォルト値、値が設定されていない時の振る舞いなど、この機会に見直せて良かったかなと思います。

覚えておくと便利そうなので、使えそうなところでは使っていきましょう。