SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 已更新 2026-10-21

Docker Compose 的 .env、env_file 與 environment 差異

了解 Docker Compose 中 .env、env_file 與 environment 的差異、優先順序,以及為何 .env 不會自動載入容器;密碼應改放 secrets。

所謂 env file 的 3 種機制

Docker Compose 有 3 種名稱相近、用途卻不同的機制。.env 檔案會在 Compose 讀取 YAML 之後、驗證或執行任何內容之前,將 compose.yaml 內的 ${VARIABLE} 佔位符代入。env_file: 屬性會將鍵值組載入容器的環境。environment: 屬性則會直接在 compose 檔案中設定容器的環境變數。這些機制不可互換。若其中 2 種機制設定了相同的鍵,則由文件定義的優先順序決定最終值。

本指南會逐一示範這些機制,並使用可執行的命令驗證優先順序。接著說明更重要的問題:任何能執行 docker inspect 的人都能讀取環境變數,因此不應將密碼放在其中。如果你還不熟悉 compose 檔案,請先閱讀 VPS 上的 Docker Compose 基礎,再回到這裡了解設定方式。

.env 檔案是給 compose 檔案使用的,不是給容器使用的

建立一個目錄,並在其中放入 2 個檔案。

mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .env
services:
  demo:
    image: alpine:${ALPINE_TAG}
    command: printenv ALPINE_TAG

現在要求 Compose 顯示它實際解析出的內容。

docker compose config

輸出會顯示 image: alpine:3.20。placeholder 已消失,因為 Compose 載入檔案時就已完成插值。Compose 會在專案目錄中尋找 .env:如果設定了 --project-directory,則使用該參數指定的目錄;否則使用透過 -f 傳入的第一個檔案所在目錄;如果兩者皆未指定,則使用目前目錄。Compose 會替換 YAML 值中的 ${NAME} placeholder。索引鍵不會進行插值,而連續兩個 $$ 會產生字面上的美元符號,不會進行替換。

接著啟動服務。

docker compose run --rm demo

printenv ALPINE_TAG 會以狀態碼 1 結束,且不輸出任何內容。該變數不存在於容器內。這是最常見的誤解:.env 設定的是 compose 檔案,而不是程序。除非 compose 檔案中的某個部分有參照 .env 檔案,否則即使檔案內含有 POSTGRES_PASSWORD=hunter2,對資料庫也完全不起作用。

${NAME:-default} 會在變數未設定或值為空時提供預設值。${NAME:?message} 會讓 Compose 拒絕啟動並輸出指定訊息;對於沒有安全預設值的設定,這是正確的選擇。

env_file 會將變數載入容器

env_file: 屬性會指定一或多個檔案,檔案內容會成為容器環境變數。

printf 'GREETING=from_env_file\nAPP_MODE=production\n' > app.env
services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    env_file:
      - ./app.env
docker compose run --rm demo

這會輸出 from_env_file。檔案格式是純 KEY=value 行,每行一個值,並以 # 開頭表示註解。它不是 shell 語法,但 Compose 會依照幾項類似 shell 的規則解析。前後引號會被移除,因此 KEY="value" 與 KEY=value 會產生相同結果。雙引號和未加引號的值會插入 ${...} 佔位符;單引號的值則會依字面使用。可以使用 export 前綴,但不需要。= 符號兩側的空格會被移除。需要注意內嵌註解:在未加引號的值中,空格後接 # 會開始註解,因此包含 # 的值都應加上引號。

如果找不到 env_file 路徑,就會發生錯誤,Compose 也會停止。若檔案可能合理地不存在,請將其標記為選用:

    env_file:
      - path: ./app.env
        required: false

environment 會在行內設定變數

services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    environment:
      GREETING: from_environment

支援兩種語法:上述的對映格式,以及使用 - GREETING=from_environment 的清單格式。兩者的行為完全相同。這兩種格式還有一個額外特性:不指定值的索引鍵,會從您執行 docker compose 的 shell 傳遞該變數。在清單格式中,這是單獨的 - GREETING;在對映格式中,則是 GREETING:,冒號後不接任何內容。

    environment:
      - GREETING
GREETING=from_my_shell docker compose run --rm demo

這會輸出 from_my_shell。如果未在 shell 中設定 GREETING 就執行,Compose 不會設定任何值,也不會顯示警告。這種無訊息的傳遞失敗值得注意,因為服務即使取得空的密碼變數,通常仍會成功啟動,而且會完全暴露在外。

誰的優先順序較高

Docker 說明了優先順序,從高到低依序為:命令列上的 docker compose run -e,接著是 environment 或 env_file,其值會從 shell 或 env 檔案插值取得;然後是 compose 檔案中的一般 environment,再來是 env_file,最後是建置於映像中的 ENV 指示詞。

日常使用時可簡化記憶為:environment: 的優先順序高於 env_file:,而命令列上的 -e 高於兩者。以下在單一檔案中驗證。

services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    env_file:
      - ./app.env
    environment:
      GREETING: from_environment
docker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETING

第一個指令會輸出 from_environment,表示 environment: 覆寫了 app.env 中的值。第二個指令會輸出 from_cli。compose 檔案中的任何設定都無法覆寫命令列選項。

如果容器的行為看起來像是設定未生效,請勿臆測。docker compose config 會輸出完整解析後的檔案,docker compose config --environment 會輸出 Compose 目前使用的插值變數。多數「我的 env 檔案被忽略」的問題,最後都證實同一個值是在兩個不同層級中重複設定。

環境變數為何會洩漏

在 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 secrets 將值保存在檔案中

Compose 支援以檔案為基礎的 secrets。值會以檔案形式掛載到容器中,而不是注入環境變數。

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
secrets:
  db_password:
    file: ./db_password.txt

secret 會掛載在容器內的 /run/secrets/db_password。斜線後的名稱,是頂層 secrets: 區塊中的 secret 名稱。

_FILE 後綴是 Docker Official Images 採用的慣例,包括 postgres、mysql 和 mariadb。這些 entrypoint 指令碼會檢查 VARNAME_FILE、讀取檔案,並使用其內容。這不是 Docker 的功能,因此只有映像檔實作該慣例時才有效。在假設 SOMETHING_FILE 會受到支援前,請先查看映像檔文件。不支援此慣例的應用程式通常可以在啟動時自行讀取檔案,或傳入檔案路徑,再由自訂的 entrypoint 處理。

從執行中的容器內驗證:

docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORD

第一個指令會印出密碼。第二個指令不會輸出任何內容,因為該值從未進入環境變數。這正是目的:此容器中的 docker inspect 只會顯示無害的路徑。

請保護主機上的來源檔案,因為 secret 的私密性取決於其背後的檔案:

chmod 600 db_password.txt

VPS 上的實用折衷方案

許多自架映像檔不支援 _FILE 變數,因此只能透過環境變數傳入設定。在只有一名管理員的 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.env

install -m 600 建立檔案時就會設定檔案模式,因此不會出現所有使用者都能讀取的時間窗口。檔案由 root 擁有,因此主機上的非 root 使用者無法讀取;但任何能執行 docker 的人,仍可從容器中讀出這些值。將 *.env 和 .env 加入 .gitignore,並提交一份 app.env.example,其中只保留金鑰名稱,值則留空。已提交的密碼就必須視為已輪替的密碼。

輪替值意味著重新啟動服務。容器程序啟動時只會讀取一次環境變數,因此編輯檔案後,必須執行 docker compose up -d --force-recreate db 才會生效。VPS 上透過 HTTPS 執行 n8n 指南也採用相同模式,將加密金鑰存放在 compose 檔案之外。

依環境拆分設定

Compose 預設會從專案目錄讀取 .env。使用 --env-file 將其指定到其他位置。

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

Compose 會依序讀取多個檔案,後讀取的檔案會覆寫先前檔案的設定。將非機密的預設值保存在已提交至版本控制的檔案中,並將 secret 保存在不離開伺服器的檔案中。env_file: 也適用相同規則;若有重複的 key,最後列出的檔案會優先。

FAQ

為什麼容器內的 .env 檔案會被忽略?

它並未被忽略。.env 檔案會替換 compose 檔案中的 ${NAME} 佔位符,也可以設定 Compose 自身的變數,例如 COMPOSE_PROJECT_NAME 或 COMPOSE_FILE。它不會設定容器內的變數。若要將值傳入容器,請以 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。

這篇文章對您有幫助嗎?