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 -epipefail

預設情況下,通道指令 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

不要在建立指令中 catecho 或列印金鑰內容;應把 /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


第 50 页,共 196 页
使用 mdPress 构建