7.1 RUN 執行指令
何時使用 RUN
RUN 是 Dockerfile 中最常用的指令,主要用於在映象建立階段執行指令來修改映象。具體來說:
- 安裝依賴:
RUN apt-get install nginx - 編譯程序:
RUN gcc -o app main.c - 下載檔案:
RUN curl -O https://example.com/file.tar.gz - 設定系統:
RUN mkdir -p /app/data
理解 RUN 的核心是理解映象分層:每一個 RUN 都會在當前層之上建立新的一層,這會影響映象大小。因此,合理使用 RUN(特別是合併多個 RUN)是建立輕量級映象的關鍵。
7.1.1 基本語法
RUN <command>
RUN ["executable", "param1", "param2"]
RUN 指令是 Dockerfile 中最常用的指令之一。它在 當前映象層 之上建立一個新層,執行指定的指令,並送出結果。
7.1.2 兩種格式對比
1. Shell 格式
RUN apt-get update
- 特點:預設透過
/bin/sh -c執行。 - 優勢:可以使用環境變數、通道、重導向等 Shell 屬性。
- 範例:
RUN echo "Hello" > /test.txt
2. Exec 格式
RUN ["apt-get", "update"]
- 特點:直接呼叫可執行檔案,不經過 Shell。
- 優勢:避免 Shell 字串解析問題,適用於引數中包含特殊字元的情況。
- 注意:無法使用
$VAR環境變數替換 (除非顯式呼叫 shell)。
7.1.3 常見最佳實踐
1. 組合指令:減少層數
每一個 RUN 指令都會新建一層映象。為了減少映象體積和層數,應使用 && 連線指令。
❌ 糟糕的寫法 (建立 3 層):
RUN apt-get update
RUN apt-get install -y nginx
RUN rm -rf /var/lib/apt/lists/*
✅ 推薦寫法 (建立 1 層):
RUN apt-get update && \
apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/*
2. 清理快取
在安裝完軟體後,立即清除快取,可以顯著減小映象體積。
-
Debian/Ubuntu:
RUN apt-get update && apt-get install -y package-bar \ && rm -rf /var/lib/apt/lists/* -
Alpine:
RUN apk add --no-cache package-bar
3. 使用 set -e 和 pipefail
預設情況下,通道指令 cmd1 | cmd2 的回傳值由最後一個指令 (cmd2) 決定,即使前面的指令失敗了,整個 RUN 仍可能視為成功。
❌ 隱蔽的錯誤:
## 如果下載失敗,gzip 可能會報錯,但如果不影響後續,建立可能繼續
RUN wget http://error-url | gzip -d > file
✅ 推薦寫法:
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN wget http://url | gzip -d > file
7.1.4 常見問題
Q:為什麼 RUN cd /app 不生效?
RUN cd /app
RUN touch hello.txt
結果:hello.txt 會出現在根目錄 /,而不是 /app。原因:每個 RUN 都在一個新的 Shell/容器環境中執行。cd 隻影響當前 RUN 的環境。解決:使用 WORKDIR 指令。
WORKDIR /app
RUN touch hello.txt
Q:環境變數不生效?
RUN export MY_VAR=hello
RUN echo $MY_VAR
結果:輸出為空。原因:同上,環境變數只在當前 RUN 有效。解決:使用 ENV 指令,或在同一行 RUN 中匯出。
ENV MY_VAR=hello
RUN echo $MY_VAR
7.1.5 高階技巧
1. 使用 BuildKit 的掛載快取
BuildKit 支援在 RUN 指令中使用 --mount 掛載快取,加速建立。
## 快取 apt 套件
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y gcc
## 快取 Go 模組
RUN --mount=type=cache,target=/go/pkg/mod \
go build -o app
2. 掛載金鑰
安全地使用 SSH 金鑰或 Token,而不將其記錄在映象中。
RUN --mount=type=secret,id=mysecret \
my-private-tool --token-file /run/secrets/mysecret
不要在建立指令中 cat、echo 或列印金鑰內容;應把 /run/secrets/<id> 路徑交給真正消費金鑰的工具。
3. Heredoc 語法
BuildKit 支援使用 heredoc 語法編寫多行指令碼,無需行末反斜槓 \ 連線:
RUN <<EOF
apt-get update
apt-get install -y \
build-essential \
curl \
git
rm -rf /var/lib/apt/lists/*
EOF
優勢:
- 可讀性更強,不需要在每行末尾新增
\ - 避免在指令碼中轉義引號
- 支援多個 heredoc 區塊,可指定不同的 Shell
RUN <<EOF
echo "使用預設 /bin/sh"
EOF
RUN <<'EOF'
#!/bin/bash
set -euo pipefail
echo "使用 bash"
wget http://example.com/file.tar.gz
EOF
使用 heredoc 需要在 Dockerfile 首行宣告 BuildKit 語法版本:
# syntax=docker/dockerfile:1