Docker Compose .env、env_file 與 secrets 差異
Docker Compose 有 3 種常被稱為 env file 的機制。了解 .env、env_file 與 environment 的差異、優先順序,以及密碼應放在 secrets 的原因。
人們所稱的 env file 的 3 種事物
Docker Compose 有 3 種名稱相近但彼此獨立的機制。.env 檔案會在 Compose 解析檔案之前,先代入 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' > .envservices:
demo:
image: alpine:${ALPINE_TAG}
command: printenv ALPINE_TAG現在請 Compose 顯示它實際解析的內容。
docker compose config輸出會顯示 image: alpine:3.20。由於插值是在解析時進行,因此 placeholder 已消失。Compose 會在 project directory 中尋找 .env。project directory 是存放 compose 檔案的目錄。Compose 會替換其中找到的所有 ${NAME}。
接著執行服務。
docker compose run --rm demoprintenv ALPINE_TAG 會以狀態碼 1 結束,且不輸出任何內容。該變數不存在於容器內。這是最常見的誤解:.env 設定的是 compose 檔案,而不是程序。單獨建立包含 POSTGRES_PASSWORD=hunter2 的 .env 檔案,對資料庫完全沒有作用,除非 compose 檔案中的某個部分參照該檔案。
${NAME:-default} 會在變數未設定或為空時提供 fallback。${NAME:?message} 會讓 Compose 拒絕啟動並輸出指定訊息。對於沒有安全預設值的設定,這是正確的選擇。
env_file 將變數載入容器
env_file: 屬性會指定一或多個檔案,這些檔案的內容會成為容器環境變數。
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。檔案格式是純 KEY=value 行,每行一個變數,使用 # 開頭表示註解。它不是 shell。引號在大多數情況下會保留為值的一部分,也不需要 export 前綴。請勿在 = 符號周圍加入空格,因為 KEY = value 會產生一個名稱為 KEY 的變數,且其值開頭包含空格。
找不到 env_file 路徑會產生錯誤,Compose 會停止。若檔案可能正常不存在,請將其標示為選用:
env_file:
- path: ./app.env
required: falseenvironment 會以行內方式設定變數
services:
demo:
image: alpine:3.20
command: printenv GREETING
environment:
GREETING: from_environment系統接受兩種語法:上述的對映形式,以及使用 - GREETING=from_environment 的清單形式。兩者的行為完全相同。清單形式另有一項特性:沒有值的裸鍵,會從您執行 docker compose 的 shell 傳遞該變數。
environment:
- GREETINGGREETING=from_my_shell docker compose run --rm demo這會列印 from_my_shell。如果未在 shell 中設定 GREETING 就執行它,Compose 不會設定任何值,也不會顯示警告。請注意這類無訊息的傳遞失敗,因為使用空白密碼變數啟動的服務通常仍能成功啟動,但實際上完全未受保護。
哪一個優先
Docker 記載的優先順序由高至低如下:命令列上的 docker compose run -e,接著是其值從 shell 或 env 檔案插入的 environment 或 env_file,再來是 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_environmentdocker 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 會將該值輸出到終端機,因此可能被貼到支援論壇中。容器內的任何程序都能讀取 /proc/1/environ,而且每個子程序都會繼承此變數。此外,應用程式的當機處理常會將完整環境傾印到日誌或錯誤報告中。
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.txtsecret 會掛載在容器內的 /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.txtVPS 上的務實折衷方案
許多自架映像檔不支援 _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.envinstall -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系統會依序讀取多個檔案,後讀取的檔案會覆寫先前檔案的設定。將非機密的預設值保存在已提交至版本控制的檔案中,並將機密資訊放在不會離開伺服器的檔案中。env_file: 也是如此;若有重複的索引鍵,最後列出的檔案所定義的值會生效。
FAQ
為什麼容器內的 .env 檔案會被忽略?
它並未被忽略。.env 檔案只會在 compose 檔案中替換 ${NAME} 佔位符。它不會設定容器內的變數。若要將值傳入容器,請參照該值: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 檔案中使用引號和空格嗎?
請使用 KEY=value with spaces,並省略引號。Compose 會將該行其餘的完整內容視為值,因此引號通常會成為值中的字面字元。= 前後不得加入空格,否則鍵結尾會帶有空格,導致任何內容都無法與其匹配。