Docker Composeの.env、env_file、environmentの違い
Docker Composeでenv fileと呼ばれる3つの仕組みを整理します。.env、env_file、environmentの違い、優先順位、秘密情報をSecretsに置く理由をコマンドで確認できます。
env ファイルと呼ばれる3つの仕組み
Docker Compose には、名前がよく似ていて混同しやすい3つの仕組みがあります。.env ファイルは、Compose が YAML を読み込んだ後、検証や実行を行う前に、compose.yaml 内の ${VARIABLE} プレースホルダーを置き換えます。env_file: 属性は、key/value の組を記述したファイルをコンテナの環境変数として読み込みます。environment: 属性は、compose ファイル内に記述した変数をコンテナへ直接設定します。これらは相互に置き換えられるものではありません。2つが同じキーを設定する場合、どちらが優先されるかは文書化された優先順位で決まります。
このガイドでは、それぞれの仕組みを実際に動作させ、実行可能なコマンドで優先順位を確認します。その後、より重要な点を説明します。環境変数は docker inspect を実行できるすべてのユーザーから読み取れるため、パスワードを環境変数に保存してはいけません。compose ファイル自体に不慣れな場合は、まず VPS 上の Docker Compose の基礎 を読み、設定についてはこのガイドに戻ってください。
.env ファイルは compose ファイル用であり、コンテナ用ではありません
ディレクトリを作成し、その中に2つのファイルを配置します。
mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .envservices:
demo:
image: alpine:${ALPINE_TAG}
command: printenv ALPINE_TAG次に、Compose が実際に解析した内容を確認します。
docker compose config出力には image: alpine:3.20 と表示されます。Compose がファイルを読み込む際に補間を実行するため、プレースホルダーはなくなっています。Compose はプロジェクトディレクトリ内の .env を探します。--project-directory で指定した場合はそのディレクトリ、それ以外の場合は -f で渡した最初のファイルのディレクトリ、どちらでもない場合は現在のディレクトリです。YAML の値に含まれる ${NAME} プレースホルダーを置換します。キーは補間されません。また、$$ を2つ重ねると、置換ではなくリテラルのドル記号になります。
次に、サービスを実行します。
docker compose run --rm demoprintenv ALPINE_TAG はステータス1で終了し、何も出力しません。コンテナ内にはその変数が存在しないためです。これが最もよくある誤解です。.env は compose ファイルを設定するものであり、プロセスを設定するものではありません。POSTGRES_PASSWORD=hunter2 を含む .env ファイルを配置しても、compose ファイルのどこかで参照しない限り、データベースには何の効果もありません。
${NAME:-default} は、変数が未設定または空の場合に代替値を指定します。${NAME:?message} を使うと、Compose は起動を拒否し、指定したメッセージを出力します。安全なデフォルト値がない場合に適した方法です。
env_file でコンテナに変数を読み込む
env_file: 属性には、内容をコンテナの環境変数にするファイルを 1 つ以上指定します。
printf 'GREETING=from_env_file\nAPP_MODE=production\n' > app.envservices:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.envdocker compose run --rm demoこれは from_env_file を出力します。ファイル形式は、1 行に 1 つのプレーンな KEY=value 行です。# で始まる行はコメントになります。これは shell ではありませんが、Compose は shell に似たいくつかの規則に従って解析します。前後の引用符は削除されるため、KEY="value" と KEY=value は同じ結果になります。二重引用符付きの値と引用符なしの値では ${...} プレースホルダーが展開されます。一重引用符付きの値はそのまま使用されます。export プレフィックスも使用できますが、不要です。= 記号の前後にある空白は削除されます。注意が必要なのは行内コメントです。引用符なしの値では、空白に続く # がコメントの開始を示します。そのため、 # を含む値は引用符で囲んでください。
env_file パスが存在しない場合はエラーになり、Compose は停止します。ファイルが正当に存在しない場合があるなら、optional として指定します。
env_file:
- path: ./app.env
required: falseenvironment で変数をインライン指定する
services:
demo:
image: alpine:3.20
command: printenv GREETING
environment:
GREETING: from_environment使用できる構文は、上記のマッピング形式と - GREETING=from_environment を使うリスト形式の2つです。動作は同じです。どちらの形式にも、もう1つ機能があります。値を指定しないキーは、docker compose を実行したシェルから変数を引き継ぎます。リスト形式では単独の - GREETING、マッピング形式ではコロンの後に何も記述しない GREETING: です。
environment:
- GREETINGGREETING=from_my_shell docker compose run --rm demoこれは from_my_shell を出力します。シェルで GREETING を設定せずに実行すると、Compose は何も設定せず、警告も表示しません。値の引き継ぎが黙って失敗する場合があることは把握しておく必要があります。空のパスワード変数でサービスが起動すると、起動自体は成功しても、実質的に無防備になることがあるためです。
どれが優先されるか
Docker では、優先順位を上位から順に示しています。コマンドラインの docker compose run -e、次にシェルまたは env ファイルから値が補間される environment または env_file、compose ファイル内の通常の environment、次に env_file、最後にイメージへ組み込まれた ENV ディレクティブです。
日常の作業で覚えておくべき点は、environment: が env_file: より優先され、コマンドラインの -e がその両方より優先されることです。1 つのファイルで確認できます。
services:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.env
environment:
GREETING: from_environmentdocker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETING最初のコマンドは from_environment を出力します。つまり、environment: が app.env の値を上書きしました。2 番目のコマンドは from_cli を出力します。compose ファイル内の設定で、コマンドラインの指定を上書きするものはありません。
コンテナが設定を適用していないように動作するときは、推測で対応しないでください。docker compose config で完全に解決されたファイルを表示できます。docker compose config --environment では、Compose が使用している補間変数を表示できます。「env ファイルが無視される」という報告の多くは、異なる 2 つのレベルで同じ値を設定していることが原因です。
環境変数が漏えいする理由
environment: にパスワードを設定すると、コンテナの設定としてディスクに保存され、docker グループの全ユーザーから見える状態になります。
docker compose run -d --name leaky -e DB_PASSWORD=hunter2 demo sleep 300
docker inspect leaky --format '{{json .Config.Env}}'出力には "DB_PASSWORD=hunter2" が平文で含まれます。同じ値を露出させる経路は、ほかにも3つあります。docker compose config は値を端末に表示するため、その内容がサポートフォーラムに貼り付けられることがあります。子プロセスはデフォルトでこの変数を継承します。ただし、親プロセスが置き換えた環境で起動した場合は継承しません。また、PID 1 と同じユーザーで実行されるプロセスや CAP_SYS_PTRACE を保持するプロセスは、/proc/1/environ から変数を読み取れます。すべてが root として実行されるコンテナでは、すべてのプロセスが該当します。PID 1 が root として実行され、アプリケーションが非特権ユーザーで実行されている場合、そのファイルの読み取りで権限エラーになります。ただし、アプリケーション自身の環境には変数が残っています。さらに、アプリケーションのクラッシュハンドラーは、環境全体をログやエラーレポートに出力することがよくあります。
docker グループのメンバーは、ホスト上では実質的に root と同等です。そのため、これを信頼できる権限境界として扱うことはできません。VPS で最小権限のユーザーアカウントを使用する方法では、共有サーバーでこのグループへの所属を制限すべき理由を説明しています。
Compose の secret は値をファイルに保持します
Compose はファイルベースの secret に対応しています。値は環境変数に注入されるのではなく、ファイルとしてコンテナ内にマウントされます。
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txtsecret はコンテナ内の /run/secrets/db_password にマウントされます。スラッシュの後にある名前は、トップレベルの secrets: ブロックで指定した secret 名です。
_FILE サフィックスは、postgres、mysql、mariadb などの Docker Official Images で使用されている慣例です。これらの entrypoint スクリプトは VARNAME_FILE を確認し、ファイルを読み込んで、その内容を使用します。これは Docker の機能ではありません。そのため、イメージ側で実装されている場合にのみ機能します。SOMETHING_FILE が使用されることを前提にする前に、イメージのドキュメントを確認してください。対応していないアプリケーションでも、起動時にファイルを直接読み込める場合があります。また、パスを渡して独自の entrypoint で処理することもできます。
実行中のコンテナ内から確認します。
docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORD1つ目のコマンドはパスワードを表示します。2つ目のコマンドは何も表示しません。値が環境変数に入っていないためです。これがこの方法の目的です。このコンテナの docker inspect には、安全なパスだけが表示されます。
secret は背後にあるファイルと同じ程度にしか保護されません。そのため、ホスト上の元ファイルを保護してください。
chmod 600 db_password.txtVPS における現実的な中間案
多くのセルフホスト用イメージは _FILE 変数に対応していないため、環境変数が唯一の設定方法になります。1 台の管理者用 VPS では、プロジェクトディレクトリ内の全ユーザーから読み取り可能なファイルに値を置かず、Git にも含めないことが現実的な目標です。
sudo install -o root -g root -m 600 /dev/null /etc/myapp/app.env
sudo nano /etc/myapp/app.env env_file:
- /etc/myapp/app.envinstall -m 600 は、最初からモードを設定した状態でファイルを作成します。そのため、全ユーザーから読み取り可能になる時間が発生しません。ファイルの所有者は root なので、サーバー上の root 以外のユーザーは読み取れません。ただし、docker を実行できるユーザーは、コンテナから値を読み取れます。*.env と .env を .gitignore に追加し、代わりにキー名と空の値を記載した app.env.example をコミットします。コミットされたパスワードは、ローテーションが必要なパスワードです。
値をローテーションするには、サービスを再起動します。環境変数はコンテナプロセスの起動時に 1 回だけ読み込まれるため、ファイルを編集しただけでは変更されません。変更を反映するには docker compose up -d --force-recreate db を実行します。これは、VPS 上で HTTPS 経由で n8n を公開するガイドでも使用しているパターンです。暗号化キーを compose ファイルの外部に置きます。
環境ごとに設定を分割する
Compose はデフォルトでプロジェクトディレクトリから .env を読み込みます。--env-file を使用すると、別の場所を指定できます。
docker compose --env-file .env.staging config複数のファイルは順番に読み込まれ、後のファイルが前のファイルを上書きします。Secret ではないデフォルト値はコミットするファイルに保持し、Secret はサーバーから外部に持ち出さないファイルに保存します。env_file: も同様で、重複するキーがある場合は最後に指定したファイルの値が有効になります。
FAQ
コンテナ内で .env ファイルが無視されるのはなぜですか?
無視されているわけではありません。.env ファイルは compose ファイル内の ${NAME} プレースホルダーを置換し、COMPOSE_PROJECT_NAME や COMPOSE_FILE など、Compose 自体の変数も設定できます。コンテナ内の変数を設定することはありません。値をコンテナに渡すには、environment: { KEY: "${NAME}" } のように参照するか、代わりに env_file: ./that-file.env を使用します。
environment は env_file を上書きしますか。それとも逆ですか?
environment: が優先されます。Docker の公式ドキュメントで定義されている順序では、environment 属性が env_file 属性より上位にあり、どちらもコマンドラインの docker compose run -e より下位です。同じキーを両方に設定すると、env_file の値は通知なく使われなくなります。
Compose が使用する最終的な値を確認するにはどうすればよいですか?
docker compose config を実行すると、すべての補間を適用した、完全に解決済みの compose ファイルが表示されます。すでに実行中のコンテナでは、docker inspect <container> --format '{{json .Config.Env}}' によりプロセスが実際に受け取った値を正確に確認できます。
Compose の secrets は暗号化されますか?
いいえ。ファイルベースの secret は /run/secrets/<name> にプレーンテキストのファイルとしてコンテナへマウントされ、ホスト上の元ファイルも暗号化されません。利点は暗号化ではなく、値の範囲を限定できることです。値はコンテナ環境、docker inspect の出力、環境変数を出力するクラッシュダンプに含まれません。
env ファイルで引用符やスペースを使用できますか?
はい。Compose は外側の引用符を削除するため、KEY="value with spaces" と KEY=value with spaces はどちらも value with spaces になります。補間させない $ を値に含める場合は単一引用符を使用します。また、 # を含む値は引用符で囲んでください。引用符がない場合、その文字からコメントとして扱われます。= の前後にあるスペースは削除されるため、KEY = value でも KEY が設定されます。