目次
先に結論:補間とコンテナへの注入は別の処理
Composeで「.envに書いたのにコンテナから読めない」とき、最初に確認するのは値そのものではなく、値がどの段階で使われるかです。Docker Composeには少なくとも二つの段階があります。一つはcompose.yaml内の${VAR}をComposeが読み込む設定値で置き換える「補間」、もう一つはenvironmentまたはサービスのenv_fileで値を指定し、コンテナの環境変数として渡す「注入」です。
Project directoryの.envは既定では主に補間用です。そこへ変数を書いただけで、すべてのサービスのコンテナ内へ自動的に注入されるわけではありません。まず次の三点を分けてください。
-
Composeが使う値を読み込めているか。
-
Compose modelのenvironment等へ値が展開されたか。
-
対象コンテナの環境変数として値が渡ったか。
一つ目が成功しても、二つ目・三つ目まで成功したとは限りません。これからの診断では値そのものを表示せず、変数名、設定位置、存在するかどうかを確認します。
症状と対象を固定する
変更前に、失敗するサービス名、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をまとめて変更すると、何が原因だったか分からなくなります。変数を一つ選び、次の順で進めます。
-
失敗するservice、変数名、Compose commandを固定する。値は伏せる。
-
作業directory、Project directory、Compose fileとProfileを記録する。
-
変数の利用場所がimage、build.args、environment、service env_fileのどれか特定する。
-
shell、Project .env、CLI --env-fileなど、補間元と優先関係を確かめる。
-
docker compose config --quietで構文を検査する。 -
modelを必要な範囲で確認し、secret値は保存・共有しない。
-
必要なserviceだけenvironmentまたはenv_fileを修正する。
-
対象containerが新設定で再作成されたかを確かめる。
-
値を出さずにcontainer内でキーの有無を確認する。
-
アプリの動作を安全に再試験し、不要な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/Project | container identity、再作成 |
| containerに値があるがアプリが使わない | key名、読込時点、アプリ設定 | 起動設定、ログ、コード |
| ローカルだけ成功しCIで失敗 | CI secret scope、作業dir | workflow 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
-
Docker Docs, Environment variables in Compose: https://docs.docker.com/compose/how-tos/environment-variables/(新しいタブで開く)
-
Docker Docs, Interpolation: https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/(新しいタブで開く)
-
Docker Docs, Set environment variables within your container's environment: https://docs.docker.com/compose/how-tos/environment-variables/set-environment-variables/(新しいタブで開く)
-
Docker Docs, Environment variables precedence: https://docs.docker.com/compose/how-tos/environment-variables/envvars-precedence/(新しいタブで開く)
-
Docker Docs, Manage secrets securely in Docker Compose: https://docs.docker.com/compose/how-tos/use-secrets/(新しいタブで開く)
-
Checked: 2026-10-15
