Docker Build secrets 怎麼用?用 --secret 取代 ARG 與 ENV
Docker build 需要 npm token、私有套件憑證或 SSH 金鑰時,直接把值塞進 ARG、ENV 或 COPY 都會讓秘密靠近 image metadata、layer 或建置輸入。正確的邊界是用 docker build --secret 傳入,再用 Dockerfile 的 RUN --mount=type=secret 只在需要的指令中暫時掛載。
直接答案是:非敏感的版本號才用 ARG,刻意要讓容器繼承的 runtime 設定才用 ENV;建置期間才需要的 credential 用 secret mount,私有 Git 操作則優先用 SSH mount。 Secret mount 不會自動替你清理命令輸出或產出的檔案,所以命令本身仍不能把秘密印出或寫進 artifact。
為什麼 ARG 與 ENV 不適合放建置憑證
下面這種寫法看起來方便,卻把 token 帶進不該出現的邊界:
ARG NPM_TOKENRUN npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN && npm ciDocker 官方 Dockerfile reference 將 ARG 定義為建置時變數,並提醒不要把秘密當成 build arg;ENV 則會成為 image configuration 的一部分,之後的 layer 或 container 都可能繼承。即使某次 build 沒有直接看到明文,也不要把「沒有在終端機印出」當成「已安全保存」。
| 建置輸入 | 適合用途 | 不要拿來放 |
|---|---|---|
ARG | 非敏感版本、功能開關、cache bust 值 | 密碼、token、私鑰 |
ENV | image 有意暴露的 runtime 設定 | 不應進 container 的 credential |
RUN --mount=type=secret | 單一建置指令需要的 token 或設定檔 | 會被 COPY 或輸出檔保存的內容 |
RUN --mount=type=ssh | 私有 Git 操作使用的 SSH agent | 複製到 image 的私鑰檔 |
這也和 Docker COPY 找不到檔案的 build context 邊界不同:secret 不應為了讓 COPY 找得到而被放進 context。
用檔案 secret 安裝私有 npm 套件
如果 npm 已經使用 .npmrc 設定 registry credential,可以把它當成 file secret:
# syntax=docker/dockerfile:1FROM node:22-alpine
WORKDIR /appCOPY package*.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc,required=true \ npm ci
COPY . .CMD ["npm", "start"]建置時指定同一個 secret ID:
docker build \ --secret id=npmrc,src="$HOME/.npmrc" \ --tag example-app:build-secrets .id 連接 CLI 的來源與 Dockerfile 的 mount;target 讓 npm 使用它原本會讀取的路徑;required=true 則把「漏傳 credential」提早變成明確的 mount 錯誤。不要把 .npmrc 加到 COPY,也不要把 secret 內容插值到另一個會留在 layer 的檔案。
Docker 官方文件也支援用 environment variable 作為來源或掛載結果。需要讓命令讀取環境變數時,可用:
docker build \ --secret id=API_TOKEN,env=API_TOKEN \ --tag example-app:build-secrets .RUN --mount=type=secret,id=API_TOKEN,env=API_TOKEN \ ./scripts/private-build.sh如果使用 src=API_TOKEN 表示環境變數名稱,請明確寫 type=env;否則 Buildx 可能把它當成相對於目前目錄的檔案來源。建置腳本也要避免 set -x、debug log 或錯誤訊息把環境值印出來。
私有 Git repository 改用 SSH mount
要在 build 中執行 git clone [email protected]:... 或安裝需要 SSH 的依賴時,通常應傳 SSH agent,而不是把私鑰當檔案 secret 複製進去:
# syntax=docker/dockerfile:1FROM alpine
RUN apk add --no-cache git openssh-clientRUN --mount=type=ssh \ git clone [email protected]:example/private-repo.git /srcdocker buildx build --ssh default --tag example-private-build .SSH mount 和 secret mount 都是暫時掛載,但用途不同:前者連到 SSH agent socket,後者通常把 token 或設定檔掛成檔案/環境變數。先看工具需要哪種 credential,再選 mount 類型。
注意 secret 不會自動讓 cache 失效
Docker 官方 cache 文件明確指出,secret 的內容不參與 build cache checksum。也就是說,token 變更不會因為內容不同而自動重跑同一個 RUN。如果命令的輸出真的會隨 secret 變更,應另外傳入不含秘密的 cache bust 值:
ARG CACHEBUST=0RUN --mount=type=secret,id=npmrc,target=/root/.npmrc,required=true \ npm cidocker build \ --secret id=npmrc,src="$HOME/.npmrc" \ --build-arg CACHEBUST=2 \ --tag example-app:build-secrets .CACHEBUST 只應是版本或計數器,不要把 token 本身放進 ARG 來觸發 cache。若秘密只影響認證、不影響輸出,則不要為了輪替 token 而破壞整個依賴安裝 cache。
在 GitHub Actions 中傳給 Buildx
CI 不應把 token 寫進 workflow YAML。Docker 的 GitHub Actions 文件提供 secret-envs 與 secret-files,把 GitHub Secret 映射到 BuildKit secret:
name: Build image
on: push:
jobs: docker: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: docker/setup-buildx-action@v4 - uses: docker/build-push-action@v7 env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} with: context: . push: false secret-envs: | npm_token=NPM_TOKEN tags: example/app:testDockerfile 端對應:
RUN --mount=type=secret,id=npm_token,env=NPM_TOKEN,required=true \ npm run build這個 workflow 仍要依 repository 的 trigger、fork PR 權限與 action pinning 做安全審查;secret-envs 只解決 credential 進入 BuildKit 的路徑,不會自動允許不可信的 pull request 讀取 production secret。
發布前驗證秘密沒有進 image
至少做一次不含真實 credential 的測試,並檢查建置輸出:
RUN --mount=type=secret少了required=true時,確認你知道工具會如何失敗。- 故意讓命令只讀取 mount,不把值寫到工作目錄或輸出 artifact。
- 用
docker history --no-trunc <image>檢查 command 文字沒有出現 token。 - 用
docker image inspect <image>檢查Config.Env沒有不應公開的 credential。 - 檢查 CI log 沒有
set -x、debug dump 或錯誤堆疊洩漏秘密。
這些檢查不能取代 credential rotation;如果秘密曾經放進 Git、image 或公開 log,先撤銷並輪替,再修正 Dockerfile。安全的 mount 只保護正確使用它的路徑。
把規則記成一句話:建置需要秘密,就讓它只在一個 RUN 指令裡短暫出現;不要讓秘密成為 Dockerfile 的參數、環境設定或複製檔案。
常見問題
Q: ARG 真的一定會把 token 以明文放進 image 嗎?
A: 不應把它當成可接受的秘密儲存方式。Docker 官方文件提醒 build args 可能出現在 image history 或 provenance 等建置 metadata;即使某次輸出沒有直接顯示,仍不值得把 credential 放進 ARG。
Q: secret mount 會不會自動把秘密從命令輸出移除?
A: 不會。mount 只提供暫時的檔案或環境輸入;命令若把它印出、寫進輸出檔或產生含 credential 的 cache,仍可能洩漏。要把命令 log、cache 與 artifact 一起檢查。
Q: 私有 Git clone 要用 secret mount 還是 SSH mount?
A: 使用 SSH agent 或 SSH key 的 Git 操作優先考慮 --mount=type=ssh;HTTP token、npm 設定檔或其他一般 credential 則用 --mount=type=secret。選擇取決於工具的認證介面,不是單純看 secret 字樣。
參考資料:
Dockerfile reference:RUN —mount=type=secret
回報錯字、失效連結,或告訴我你想看的延伸主題。