🐳

.envでDocker開発環境をカスタマイズ!

こんにちは、エンジニアの籏野です。

以前、弊社のエンジニアが社内の Dockerfile のベストプラクティスを公開しました。
https://zenn.dev/forcia_tech/articles/20210716_docker_best_practice

この頃から更に Docker を用いるための知見が増えてきており、アプリ開発時にはコンテナを利用することが当たり前になってきました。
新規アプリはもちろんのこと、昔からあるアプリに対してもコンテナによる開発環境を用意する動きが多くあります。

コンテナ起動には docker-compose(もしくは docker compose プラグイン)を利用していますが、いろんな開発者がコンテナを利用していると、「自分の環境では少しだけ設定を変えたい!」という場面が出てきます。
そんなニーズに応える docker compose の設定方法を紹介します。

3 行まとめ

  • compose.yml の設定値を変数化しておく
  • 変数は.env で定義する
  • .env をシェルスクリプトで生成する

設定ファイルの用意

筆者の環境では docker compose プラグインを使ってコンテナを立ち上げています。
今回はデータベースのコンテナを立ち上げる時の設定を例として用意しました。

# compose.yml
services:
  postgres:
    image: postgres:11-alpine
    environment:
      TZ: Asia/Tokyo
      PGDATA: /data/pgdata
      POSTGRES_PASSWORD: password
    healthcheck:
      test: pg_isready
      interval: 10s
      timeout: 5s
      retries: 5
    ports:
      - 5432:5432

DB にコンテナ外部から接続できるよう 5432 ポートをホスト側に開放していますが、ここで問題が生じることが多いです。

フォルシアでは一人の開発者が複数のプロジェクトに携わります。
そのためプロジェクト毎に用意された設定ファイルを使ってコンテナを起動することになるのですが、異なるプロジェクトで同じポートを利用するように設定されている場面に多く遭遇します。
この場合利用したいポートが被っているので、以下のようなエラーが出てコンテナを起動することができません。

Bind for 0.0.0.0:5432 failed: port is already allocated

最初は一時的な対応として、設定ファイルの書き換えていました。
ただこれだと、設定ファイルをリポジトリ管理している場合は差分として検出されてしまいますし、誤ってプッシュしてしまうリスクもあります。

.env ファイルの利用

先の状態を回避するために、compose.yml 内の設定値を環境変数で埋め込むようにし、実際の値は.env ファイルで定義するようにした。
.env ファイルを compose.yml と同じ階層に置くことで、docker compose プラグインが変数を展開してくれます。
作成した.env ファイルと compose.yml は以下のようになりました。

# .env
COMPOSE_PROJECT_NAME=sample_project

TZ=Asia/Tokyo
PGDATA=/data/pgdata
POSTGRES_PORT=5432
POSTGRES_PASSWORD=password
# compose.yml
services:
  postgres:
    image: postgres:11-alpine
    environment:
      TZ: ${TZ}
      PGDATA: ${PGDATA}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: pg_isready
      interval: 10s
      timeout: 5s
      retries: 5
    ports:
      - ${POSTGRES_PORT}:5432

開発者独自に値を変えたい場合は、.env に記載の値を変更してもらいます。
.env ファイルを gitignore に追加することで、差分が出てしまう問題を回避できました。

より柔軟に.env ファイルを生成する

ここまでの対応でも十分かと思いますが、より快適な開発環境のためにもう少し工夫します。
compose.yml に新たなコンテナを追加するときなどには、.env にも変数を追加する必要が出てきます。
この場合、.env はリポジトリ管理されていないので各開発者が手元の.env に追加内容を記載する必要があります。
追加量が多かったり、頻度が多かったりするとなかなか面倒になります。

この課題を解決するために.env を生成するスクリプトを用意しました。

#!/bin/bash
set -eu

ENV_FILE_PATH=/path/to/.env

if [[ -e ${ENV_FILE_PATH} ]]; then
	. ${ENV_FILE_PATH}
fi

cat << EOF > ${ENV_FILE_PATH}
COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-sample_project}

TZ=${TZ:-Asia/Tokyo}
PGDATA=${PGDATA:-"/data/pgdata"}
POSTGRES_PORT=${POSTGRES_PORT:-5432}
POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password}
EOF

ポイントは以下の部分になります。

if [[ -e ${ENV_FILE_PATH} ]]; then
	. ${ENV_FILE_PATH}
fi

シンプルに.env を生成するスクリプトを作成すると、開発者が既に設定した値を消してしまうことになります。
これを回避するために、.env ファイルが存在すればその値を使いまわすようにしました。
また各変数にデフォルト値を指定しておくことで、初回作成時にも問題なく処理が終わるようにしています。

何らかの変数が追加された場合には上記スクリプトを実行するだけでよくなったのでかなり快適です。

最後に

一昔前はプロジェクト毎にミドルウェアのバージョンを切り替えて、シンボリックリンクを切り替えて。。と開発環境を立ち上げるのも一苦労でした。
コンテナを利用することで、コマンド一発で開発環境を立ち上げられるのは最高です。

みなさんも快適なコンテナライフを!

【おまけ】その他の手法

他にもいくつか手法を調査・検討しましたので、参考までに記載しておきます。

compose.yml の env_file オプションの利用

デフォルトの設定を.env に、各人の設定を.env.local に記載するようにします。
これらのファイルを env_file オプションに指定することで、立ち上げたコンテナ内で環境変数として利用できます。
以下のように記載することで、.env.local(=個人の設定)がある場合にはそちらを優先して利用することになります。

services:
  postgres:
    ...
    env_file:
      - .env
      - .env.local
    ...

ただ、この方法だとコンテナ内の環境変数にしか適用されず、compose.yml で設定する項目自体を上書くことができませんでした。

compose コマンドの--env-file オプションの利用

先と同じように.env と.env.local を用意します。
これらのファイルを docker compose コマンドの--env-file オプションに指定することで、compose.yml で設定した項目自体を上書きすることができ、より柔軟な設定が可能になります。

docker compose --env-file .env --env-file .env.local up -d

ただ、docker compose コマンド実行時に毎回オプションを指定するのは手間なので今回は採用しませんでした。

compose.yml 内で環境変数のデフォルト値を指定する

.env ファイルのみを用意し、各人の設定は.env ファイルに記載するようにします。
また各環境変数のデフォルト値は compose.yml で記載することで、本記事で紹介したのと近い形でコンテナを立ち上げることができるようになります。

services:
  postgres:
    image: postgres:11-alpine
    environment:
      TZ: ${TZ:-Asia/Tokyo}
      PGDATA: ${PGDATA:-/data/pgdata}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
    healthcheck:
      test: pg_isready
      interval: 10s
      timeout: 5s
      retries: 5
    ports:
      - ${POSTGRES_PORT:-5432}:5432

ただこの場合、以下のような問題点があります。

  • compose.yml 内で同じ環境変数を複数回利用している場合、全ての環境変数にデフォルト値を設定する必要がある。
  • 環境変数にどのような値が入っているかを確かめるために、compose.yml と.env を両方確認する必要がある。

この記事を書いた人

籏野 拓
2018 年新卒入社
ふるさと納税で頼んだフルーツを楽しみにしています。

FORCIA Tech Blog

Discussion