目錄

Docker 環境變數與 .env 檔設定

同一個映像檔,在開發、測試、正式環境要連不同的資料庫、用不同的設定。最常見的做法是把這些設定放在環境變數裡,執行容器時再傳入。

用 -e 設定

$ docker run --rm -e NODE_ENV=production -e PORT=8080 alpine:3.23 sh -c "echo \$NODE_ENV \$PORT"
production 8080

$ export API_KEY=abc123 && docker run --rm -e API_KEY alpine:3.23 sh -c "echo API_KEY=\$API_KEY"
API_KEY=abc123
  • -e 名稱=值 直接設定。
  • 只寫 -e 名稱(不寫值)會沿用主機目前的同名環境變數。

用 --env-file 讀取檔案

變數一多,寫在指令裡很難管理。把它們放進檔案:

$ cat app.env
# app.env
NODE_ENV=production
PORT=8080
GREETING="hello world"
DATABASE_URL=postgres://app:secret@db:5432/app

$ docker run --rm --env-file app.env alpine:3.23 env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=e311730363f7
NODE_ENV=production
PORT=8080
GREETING="hello world"
DATABASE_URL=postgres://app:secret@db:5432/app
HOME=/root

注意 GREETING 那一行:檔案裡寫的是 "hello world",容器裡拿到的值包含引號。docker run --env-file 的規則是:

  • 一行一個 KEY=VALUE,# 開頭是註解。
  • 值不要加引號,引號會被當成值的一部分。
  • 不支援 $OTHER_VAR 的變數展開。

(下面會看到,Compose 讀取同一個檔案時會把引號去掉,兩者行為不同。)

在 Dockerfile 設定預設值

ENV NODE_ENV=production \
    PORT=3000

ENV 設定的是預設值,docker run -e 可以覆蓋它。適合放「不太會變、也不是機密」的設定。

優先順序

環境變數的優先順序:docker run -e 或 compose environment 最高,其次 env-file,再來是 Dockerfile ENV;.env 則用於替換 compose.yaml 中的變數
環境變數的優先順序,以及 .env 的不同用途

從高到低:

  1. docker run -e
  2. docker run --env-file
  3. Dockerfile 的 ENV
  4. 基礎映像檔裡的 ENV

環境變數看得到嗎?

環境變數方便,但不算安全。任何能執行 docker inspect 的人都看得到:

$ docker run -d --name envdemo -e SECRET_TOKEN=abc123 alpine:3.23 sleep 60
431168d97dc8187ec79e420638155b2f53fd78a9d82f8a6d13eb6bd9aec09bcb

$ docker inspect --format '{{json .Config.Env}}' envdemo
["SECRET_TOKEN=abc123","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]

$ docker rm -f envdemo
envdemo

此外,程式出錯時環境變數可能被印進日誌,子程序也會繼承。所以密碼、API 金鑰請注意下面「處理機密資料」的建議。

Docker Compose 中的環境變數

Compose 有兩種容易搞混的「環境變數」,用一個例子說明。compose.yaml:

services:
  app:
    image: alpine:3.23
    command: ["sh", "-c", "env | sort | grep -E 'APP_|DATABASE|NODE_ENV|PORT|GREETING'"]
    environment:
      APP_VERSION: ${APP_VERSION}
      DATABASE_PASSWORD: ${DB_PASSWORD}
    env_file:
      - app.env

1. 傳給容器的變數:environment 與 env_file

environment 直接寫在 compose.yaml 裡;env_file 讀取檔案(這裡沿用上面的 app.env)。

2. 用來替換 compose.yaml 內容的變數:.env

Compose 會自動讀取專案資料夾中的 .env 檔,用來替換 compose.yaml 裡的 ${...}。用 docker compose config 可以看到替換後的最終設定:

$ cat .env
APP_VERSION=1.4.2
DB_PASSWORD=change-me

$ docker compose config app
name: ch15
services:
  app:
    command:
      - sh
      - -c
      - env | sort | grep -E 'APP_|DATABASE|NODE_ENV|PORT|GREETING'
    environment:
      APP_VERSION: 1.4.2
      DATABASE_PASSWORD: change-me
      DATABASE_URL: postgres://app:secret@db:5432/app
      GREETING: hello world
      NODE_ENV: production
      PORT: "8080"
    image: alpine:3.23
    networks:
      default: null
networks:
  default:
    name: ch15_default

$ docker compose run --rm app
[+] run 1/1
 ✔ Network ch15_default Created                                                                 0.0s
Container ch15-app-run-dfbcf95b7271 Creating
Container ch15-app-run-dfbcf95b7271 Created
APP_VERSION=1.4.2
DATABASE_PASSWORD=change-me
DATABASE_URL=postgres://app:secret@db:5432/app
GREETING=hello world
NODE_ENV=production
PORT=8080
  • ${APP_VERSION}、${DB_PASSWORD} 被換成了 .env 裡的值。
  • app.env 中的 GREETING="hello world" 經過 Compose 讀取後沒有引號,和 docker run --env-file 不同。
  • PORT: "8080" 被加上引號,代表它是字串(環境變數一律是字串)。

.env 本身不會自動傳進容器,它只用來替換 compose.yaml。要傳進容器,必須在 environment 裡引用(像 APP_VERSION: ${APP_VERSION}),或用 env_file: .env。

預設值與必填檢查

寫法意思
${VAR}替換成 VAR 的值
${VAR:-預設值}未設定或為空時使用預設值
${VAR:?錯誤訊息}未設定或為空時直接報錯

${VAR:?} 很適合用在密碼這類「一定要設定」的變數,忘記設定時會立即停止,而不是用空密碼啟動:

services:
  db:
    image: postgres:18
    environment:
      POSTGRES_PASSWORD: ${MISSING_PASSWORD:?請在 .env 設定 MISSING_PASSWORD}
$ docker compose -f compose.required.yaml config
error while interpolating services.db.environment.POSTGRES_PASSWORD: required variable MISSING_PASSWORD is missing a value: 請在 .env 設定 MISSING_PASSWORD

處理機密資料

  • 不要把密碼寫進 Dockerfile 的 ENV 或 ARG,它們會留在映像檔裡(ARG 的實測見 Dockerfile 指令大全)。
  • 不要把 .env 提交到 Git。在 .gitignore 加上 .env,另外提供一個沒有真實值的 .env.example。
  • 在 .dockerignore 加上 .env,避免被 COPY . . 打包進映像檔。

用檔案傳遞機密(Compose secrets)

很多官方映像檔支援 _FILE 結尾的變數,從檔案讀取密碼:

services:
  db:
    image: postgres:18
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt
$ docker compose up -d db
[+] up 1/1
 ✔ Container ch15-db-1 Started                                                                  0.3s

$ docker compose exec db sh -c "ls -l /run/secrets; cat /run/secrets/db_password; echo; env | grep POSTGRES"
total 4
-rw-rw-r-- 1 1001 1001 16 Oct 12 14:25 db_password
s3cret-from-file
POSTGRES_PASSWORD_FILE=/run/secrets/db_password

$ docker compose down
[+] down 2/2
 ✔ Container ch15-db-1  Removed                                                                 0.1s
 ✔ Network ch15_default Removed                                                                 0.1s

機密內容以檔案形式出現在容器的 /run/secrets/db_password,環境變數裡只有檔案路徑 POSTGRES_PASSWORD_FILE,docker inspect 看不到密碼本身。

建置時需要的機密

例如安裝私有套件需要 token,請用 BuildKit 的 secret 掛載:

# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
docker build --secret id=npm_token,env=NPM_TOKEN -t myapp .

token 只在這個 RUN 執行期間存在,不會留在任何一層。

重點回顧

  • 優先順序:-e/environment > --env-file/env_file > Dockerfile ENV。
  • .env 用來替換 compose.yaml 中的 ${VAR},不會自動傳進容器。
  • docker run --env-file 不會去掉值的引號,Compose 會。
  • 機密資料用檔案(secrets)傳遞,不要寫進映像檔。

動手練習

練習 1:映像檔有 ENV MODE=prod,用 docker run -e MODE=dev 執行,容器中 MODE 是什麼?

答案

dev。-e 的優先順序高於 Dockerfile 的 ENV。

練習 2:compose.yaml 寫 POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required},但 .env 中沒有 DB_PASSWORD。執行 docker compose up 會怎樣?

答案

Compose 直接報錯停止並顯示 DB_PASSWORD is required,不會用空密碼建立資料庫。

常見問題

修改 .env 後容器的環境變數沒有變?

環境變數在容器建立時決定。修改後要重建容器:Compose 用 docker compose up -d,restart 不會套用。

docker inspect 看得到我的密碼?

看得到。用 -e 傳入的值都會出現在 docker inspect 中,敏感資料請改用檔案(secrets)方式傳遞。

${VAR:-預設值} 和 ${VAR:?訊息} 差在哪?

前者在未設定時使用預設值;後者在未設定時直接報錯停止,適合必填的密碼,避免變成空字串。

更新於 2026年10月14日