Docker Composeで.envの値がコンテナに入らないとき補間とenv_fileを分ける

Composeで「.envに書いたのにコンテナから読めない」とき、最初に確認するのは値そのものではなく、値がどの段階で使われるかです。Docker Composeには少なくとも二つの段階があります。

AIものさし編集部

先に結論:補間とコンテナへの注入は別の処理

Composeで「.envに書いたのにコンテナから読めない」とき、最初に確認するのは値そのものではなく、値がどの段階で使われるかです。Docker Composeには少なくとも二つの段階があります。一つはcompose.yaml内の${VAR}をComposeが読み込む設定値で置き換える「補間」、もう一つはenvironmentまたはサービスのenv_fileで値を指定し、コンテナの環境変数として渡す「注入」です。

Project directoryの.envは既定では主に補間用です。そこへ変数を書いただけで、すべてのサービスのコンテナ内へ自動的に注入されるわけではありません。まず次の三点を分けてください。

  1. Composeが使う値を読み込めているか。

  2. Compose modelのenvironment等へ値が展開されたか。

  3. 対象コンテナの環境変数として値が渡ったか。

一つ目が成功しても、二つ目・三つ目まで成功したとは限りません。これからの診断では値そのものを表示せず、変数名、設定位置、存在するかどうかを確認します。

症状と対象を固定する

変更前に、失敗するサービス名、Composeファイル、実行コマンド、作業ディレクトリ、変数名を記録します。値は記録しません。たとえば「apiサービスでDATABASE_URLが未設定」と、キー名だけを使います。

.envやCompose設定全体をチャット、Issue、画面共有へそのまま貼らないでください。接続文字列、token、passwordが含まれることがあります。複数のComposeファイルから起動している場合は、実行時の-f指定、--project-directory、PWDも確認します。.envの探索位置は、いつも見ているcompose.yamlの隣とは限りません。Project directoryは明示指定、最初に指定したComposeファイルの場所、または現在の作業ディレクトリから決まるためです。

最初に対象のCompose commandを一字一句記録し、後の診断も同じ引数で行います。異なるCompose file、Profile、Project directory、--env-fileでconfigを確認しても、実際の起動条件を確認したことにはなりません。

変数が使われる場所を分類する

compose.yamlで変数がどこに書かれているか探します。

表は横にスクロールできます

記述主な役割それだけでコンテナ内にあるか
image: app:$Compose設定値の補間いいえ
environment: APP_MODE: $補間値をサービス環境へ注入はい
env_file: ./service.envファイルの値をサービス環境へ注入はい
build.args: VERSION: $イメージbuild時のARG実行時環境とは別
DockerfileのENVイメージの既定環境変数Compose設定で上書きされ得る

たとえば.envにAPP_MODE=productionがあり、compose.yamlにimage: app:$だけがある場合、APP_MODEはイメージタグを決めるための補間です。アプリが実行時に読む変数の注入ではありません。コンテナ内でも必要なら、該当サービスのenvironmentまたはenv_fileに別途指定します。

反対に、サービスのenv_fileへ書いた値を、Composeファイル全体の$補間にも使えると考えるのも誤りです。サービスのenv_fileはコンテナ向け、CLIの補間入力はCompose model向けです。両方必要なら、それぞれの設定場所を分けます。

Composeが読む補間元と優先順位

明示的な--env-fileがない場合、Compose CLIは通常Project directoryの.envを補間に使います。別ファイルを使う場合は、たとえば次のように指定します。


docker compose --env-file ./config/staging.env config --environment

複数の--env-fileを指定した場合、後のファイルが前の値を上書きします。同じ変数がshell環境にも設定されていると、shell側の値が補間結果へ影響します。Compose commandが読み込むファイルと、serviceのenv_fileは別物です。診断と通常起動で--env-fileの有無が違わないか確かめます。

config --environmentは補間に使う環境値を表示するため、secretを含む可能性があります。結果を端末外へコピー、ログ保存、Issue添付する前に、必要性とredactionを確認してください。実値を残す必要がない場合は表示を避け、キーの存在だけを確認する方法を選びます。必須変数が空なら早く失敗させるために${VAR:?VAR is required}のような必須指定も使えます。エラー文に値を含めてはいけません。

Compose modelの補間結果を見る

設定が構文として妥当かは次で確認できます。


docker compose config --quiet

docker compose config

--quietは構文・整合性を確認しますが、値がコンテナへ届くことまでは証明しません。通常のconfig出力は統合後のCompose modelを表示し、展開済みsecretが含まれる可能性があります。端末内だけで確認し、共有前に必ず値を伏せてください。

modelでは対象サービスのenvironment項目があるか、値が空でないか、env_fileのパスが意図したファイルを指すかを確認します。相対パスはCompose fileの場所を基準に解釈される設定があるため、実際の解決先を確かめます。image欄だけに値があれば、それはcontainer injectionの証拠ではありません。

コンテナに値があるか、値を出さずに検査する

稼働中の対象serviceなら、キーの有無だけを確認する例です。


docker compose exec api sh -lc 'test -n "${APP_MODE}" && echo SET || echo EMPTY'

まだ起動していない場合は、使い捨てrunを使えるか検討します。


docker compose run --rm --no-deps api sh -lc 'test -n "${APP_MODE}" && echo SET || echo EMPTY'

この例は、imageにshがあること、EntryPointやcommandをこの用途で安全に上書きできることが前提です。条件が違えばそのまま使わず、アプリ側の安全な診断や値の存在だけを返すhealth checkを使ってください。env、printenv、docker inspectの全出力を共有するとsecretが漏れる可能性があります。値の一致を確かめる必要があっても、secretを出力しないローカル比較を選びます。

症状別の原因候補

1. ルート.envをコンテナ注入用だと思っている

.envに値があり、compose.yamlでimageのtagなどに$を使っていて、サービスにenvironmentやenv_fileがないケースです。値を必要とするサービスにだけ注入設定を加えます。すべてのサービスへ無差別に渡さず、機密値なら必要なserviceを最小化し、Compose secrets等も検討します。

2. CLIの--env-fileとserviceのenv_fileを混同している

CLIの--env-fileはCompose file補間の入力、serviceのenv_fileはcontainer environmentへ値を渡す設定です。ファイル名が同じでも役割は別です。両方使う場合は設定箇所を明示し、相対パスと起動commandを記録してください。

3. shell環境が.envを上書きしている

ターミナル、IDE、CI、task runnerから変数が渡されていると、.envの値と異なる結果になることがあります。値は表示せず、設定されているかだけ確認します。ローカルとCIで挙動が違うなら、作業ディレクトリ、Compose CLI version、command、file precedenceを並べて比較します。変数をunsetする前に、別用途へ影響しないことも確かめます。

4. Compose fileまたはProject directoryが違う

複数ファイル、-f、-p、COMPOSE_FILE、--project-directoryを利用していると、想定した.envではない場所を参照する可能性があります。実際の起動commandを保ち、同じ条件でconfigを確認します。リポジトリ直下の.envを変更しても、Composeが別Project directoryを使っていれば反映されません。

5. environmentに空値を明示している

environmentにキーだけを書いたり、解決できない値を置いたりすると、期待した値と異なる空値や未設定になることがあります。Compose modelで空か未設定かを確認します。アプリ上空文字が正当な値の場合もあるため、空なら常にエラーと決めつけず、アプリの契約と照合します。

6. build.argsと実行時変数を混同している

build.argsはイメージのbuild時に渡す引数です。実行中アプリの環境変数として残るとは限りません。build時だけ必要かruntimeにも必要かを分けます。secretをbuild argやイメージレイヤーへ埋め込むと漏えいにつながることがあるため、BuildKit secrets等の適切な方法を検討します。

小さな再現例

値には本物のsecretでなく、秘密でないテスト値を使います。


# Project .env: interpolation input

IMAGE_TAG=local-test

APP_MODE=diagnostic


services:

  api:

    image: example/api:${IMAGE_TAG}

    environment:

      APP_MODE: ${APP_MODE}

IMAGE_TAGはimageの指定へ補間され、APP_MODEはenvironmentを経由してサービス環境に展開されます。APP_MODEを.envに書いただけで、YAMLのenvironmentにもservice env_fileにも指定しなければ、containerへ自動注入されるとは限りません。

service env_fileは別に確認します。


services:

  api:

    env_file:

      - ./api.env

api.envはサービスに対する設定であり、CLIの補間入力とは役割が異なります。テスト値でCompose modelとcontainer内の存在をそれぞれ検証し、記録には値でなくPASS/EMPTYなどの結果だけを残します。本番secretでも同じ方針を維持してください。

修正を小さくする実行順

複数の.env、YAML、Dockerfile、CI secretをまとめて変更すると、何が原因だったか分からなくなります。変数を一つ選び、次の順で進めます。

  1. 失敗するservice、変数名、Compose commandを固定する。値は伏せる。

  2. 作業directory、Project directory、Compose fileとProfileを記録する。

  3. 変数の利用場所がimage、build.args、environment、service env_fileのどれか特定する。

  4. shell、Project .env、CLI --env-fileなど、補間元と優先関係を確かめる。

  5. docker compose config --quietで構文を検査する。

  6. modelを必要な範囲で確認し、secret値は保存・共有しない。

  7. 必要なserviceだけenvironmentまたはenv_fileを修正する。

  8. 対象containerが新設定で再作成されたかを確かめる。

  9. 値を出さずにcontainer内でキーの有無を確認する。

  10. アプリの動作を安全に再試験し、不要なdebug設定を片付ける。

Compose設定を変えた後、既存コンテナに値が残っていることがあります。単なるrestartだけで新しいCompose設定が再適用されたと決めつけず、対象serviceの再作成が必要か検討します。volume、DB、永続データに影響する削除操作を診断目的で安易に実行してはいけません。

判定表

表は横にスクロールできます

観測原因候補次の確認
config --environmentにキーがない補間元・Project directory・shell--env-file、PWD、-f
modelのimageだけに値があるinterpolationだけservice environment/env_file
modelのenvironmentが空未設定、shell precedence、YAML空指定補間元と優先関係
modelには値があるがcontainerにはない古いcontainer、別service/Projectcontainer identity、再作成
containerに値があるがアプリが使わないkey名、読込時点、アプリ設定起動設定、ログ、コード
ローカルだけ成功しCIで失敗CI secret scope、作業dirworkflow command、環境
env_fileだけありYAML補間が空補間入力を別に設定Project .env / --env-file
build時だけ使えruntimeでないbuild.argsとruntime混同Build-time / environment

表は診断の入口です。一つの観測だけで原因を決めず、同一command条件で段階ごとに確認してください。

CIでだけ再現する場合

CIではローカルの.envが存在するとは限りません。CI providerのsecret storeからworkflow jobへ値が渡るか、Compose commandを実行するworking directoryが期待どおりか確認します。configの全文をCI logに出すと展開済みsecretを漏らすおそれがあります。artifactとして保存することも避けます。

秘密値そのものではなく、キーの存在や空・非空だけを検査します。ローカルとCIでCompose CLI pluginのversion、--env-file、Compose file、Profileが異なると結果も変わり得ます。条件を記録し、同じ起動設定で比較してください。

仕上げチェックリスト

  • 対象serviceと変数名を固定し、値を表示していない

  • Compose file、Project directory、作業directoryを確認した

  • Project .env / CLI --env-fileとservice env_fileを区別した

  • environmentとbuild.argsの役割を区別した

  • shellや複数env fileの優先関係を見た

  • Compose modelの補間とcontainer内の存在を別々に検査した

  • 対象containerが新しい設定で動作している

  • secretがログ、Issue、画面共有へ出ていない

  • 設定変更後にアプリの挙動を確認した

  • 削除・再作成でvolumeやDBを失わない手順を選んだ

次に行うこと

失敗しているサービスから変数を一つ選び、値を公開せず「Compose補間」「service設定」「container内の存在」を記録してください。補間だけならProject directory、shell、--env-fileを確認します。service environmentがなければ必要なserviceへenvironmentかenv_fileを設定します。container内に値があるのにアプリが使わないなら、Composeではなく、アプリの変数名、読込時点、設定層を調べます。実際のCompose file、secret store、実行環境は確認していないため、特定環境の原因はこの切り分けで判断してください。

Sources