建置最佳實務

使用多階段建置

多階段建置可讓您在映像檔建置和最終輸出之間建立更清晰的區隔,從而減少最終映像檔的大小。 將 Dockerfile 指令拆分為不同的階段,以確保產生的輸出僅包含執行應用程式所需的檔案。

使用多個階段還可以讓您透過並行執行建置步驟來更有效率地建置。

如需更多資訊,請參閱多階段建置

建立可重複使用的階段

如果您有多個映像檔有很多共同點,請考慮建立一個包含共用組件的可重複使用階段,並將您的唯一階段基於該階段。 Docker 只需建置一次通用階段。 這表示您的衍生映像檔可以更有效率地使用 Docker 主機上的記憶體,並且載入速度更快。

維護一個共同的基礎階段(「不要重複自己」)也比讓多個不同的階段執行類似的操作更容易。

選擇正確的基礎映像檔

獲得安全映像檔的第一步是選擇正確的基礎映像檔。 選擇映像檔時,請確保它是從受信任的來源建置的,並保持其大小較小。

選擇基礎映像檔時,請注意指示映像檔是否屬於這些程式的標章。

Docker Hub Official and Verified Publisher images

從 Dockerfile 建立映像檔時,請確保選擇符合需求的最小基礎映像檔。較小的基礎映像檔不僅提供可攜性和快速下載速度,還能縮小映像檔的大小,並將透過相依性引入的弱點數量降至最低。

您也應該考慮使用兩種基礎映像檔:一種用於建置和單元測試,另一種(通常更精簡)用於生產環境。在開發的後期階段,您的映像檔可能不需要建置工具,例如編譯器、建置系統和除錯工具。具有最少相依性的小型映像檔可以大幅降低受攻擊面。

經常重建您的映像檔

Docker 映像檔是不可變的。建置映像檔就是在該時刻對該映像檔進行快照。這包括您在建置中使用的任何基礎映像檔、程式庫或其他軟體。為了確保映像檔保持最新和安全,請務必使用更新的相依性經常重建映像檔。

為了確保在建置中取得最新版本的相依性,您可以使用 --no-cache 選項來避免快取命中。

$ docker build --no-cache -t my-image:my-tag .

以下 Dockerfile 使用 ubuntu 映像檔的 24.04 標籤。隨著時間推移,由於發布者使用新的安全修補程式和更新的程式庫重建映像檔,該標籤可能會解析為 ubuntu 映像檔的不同底層版本。使用 --no-cache,您可以避免快取命中,並確保重新下載基礎映像檔和相依性。

# syntax=docker/dockerfile:1
FROM ubuntu:24.04
RUN apt-get -y update && apt-get install -y --no-install-recommends python3

也請考慮鎖定基礎映像檔版本

使用 .dockerignore 排除

若要排除與建置無關的檔案,而無需重新架構您的程式碼儲存庫,請使用 .dockerignore 檔案。此檔案支援類似於 .gitignore 檔案的排除模式。

例如,若要排除所有副檔名為 .md 的檔案

*.md

有關建立一個檔案的資訊,請參閱Dockerignore 檔案

建立 Ephemeral 容器

Dockerfile 定義的映像檔應該產生盡可能短暫的容器。短暫表示可以停止和銷毀容器,然後使用絕對最少的設定和配置重建和替換容器。

請參閱十二要素應用程式方法論中的流程不要安裝不必要的套件

避免安裝額外或不必要的軟體包,只因為它們可能很好用。例如,您不需要在資料庫映像檔中包含文字編輯器。

當您避免安裝額外或不必要的軟體包時,您的映像檔將降低複雜性、減少相依性、減小檔案大小並縮短建置時間。

將應用程式分離

每個容器應該只有一個關注點。將應用程式解耦到多個容器中,可以更容易地水平擴展和重複使用容器。例如,一個 Web 應用程式堆疊可能由三個獨立的容器組成,每個容器都有自己獨特的映像檔,以解耦的方式管理 Web 應用程式、資料庫和記憶體內快取。

將每個容器限制為一個流程是一個很好的經驗法則,但這並不是一個硬性規定。例如,容器不僅可以使用 init 流程產生,某些程式也可能自行產生額外流程。例如,CeleryApacheDocker 容器網路來確保這些容器可以通訊。

排序多行參數

盡可能按字母數字順序排列多行參數,以便更容易維護。這有助於避免軟體包重複,並使清單更容易更新。這也讓 PR 更容易閱讀和審閱。在反斜線 (\) 之前添加空格也有幫助。

以下是buildpack-deps 映像檔

利用建置快取

建置映像檔時,Docker 會逐步執行 Dockerfile 中的指示,並按指定的順序執行每個指示。對於每個指示,Docker 會檢查是否可以從建置快取中重複使用該指示。

了解建置快取的工作原理以及快取失效發生的方式,對於確保更快的建置至關重要。有關 Docker 建置快取以及如何最佳化建置的詳細資訊,請參閱Docker 建置快取

固定基礎映像檔版本

映像檔標籤是可變的,表示發布者可以更新標籤以指向新的映像檔。這很有用,因為它允許發布者更新標籤以指向映像檔的較新版本。作為映像檔使用者,這表示您在重建映像檔時會自動取得新版本。

例如,如果您在 Dockerfile 中指定 FROM alpine:3.19,則 3.19 會解析為 3.19 的最新修補程式版本。

# syntax=docker/dockerfile:1
FROM alpine:3.19

在某個時間點,3.19 標籤可能指向映像檔的 3.19.1 版本。如果您在 3 個月後重建映像檔,則相同的標籤可能指向不同的版本,例如 3.19.4。此發布工作流程是最佳實務,大多數發布者都使用此標記策略,但它並非強制性的。

這樣做的缺點是您無法保證每次建置都得到相同的結果。這可能會導致重大變更,也表示您沒有正在使用的確切映像檔版本的審計追蹤。

為了完全確保您的供應鏈完整性,您可以將映像檔版本鎖定到特定的摘要。透過將映像檔鎖定到摘要,即使發布者用新映像檔替換標籤,您也可以保證始終使用相同的映像檔版本。例如,以下 Dockerfile 將 Alpine 映像檔鎖定到與先前相同的標籤 3.19,但這次也使用摘要參考。

# syntax=docker/dockerfile:1
FROM alpine:3.19@sha256:13b7e62e8df80264dbb747995705a986aa530415763a6c58f84a3ca8af9a5bcd

使用此 Dockerfile,即使發布者更新 3.19 標籤,您的建置仍將使用鎖定的映像檔版本:13b7e62e8df80264dbb747995705a986aa530415763a6c58f84a3ca8af9a5bcd

雖然這有助於您避免意外變更,但每次您想要更新它時,都必須手動查閱並包含基礎映像檔版本的映像檔摘要,這也很麻煩。而且您選擇退出自動安全修復,這可能是您想要獲得的。

Docker Scout 預設的最新基礎映像檔策略會檢查您正在使用的基礎映像檔版本是否確實是最新版本。此策略還會檢查 Dockerfile 中鎖定的摘要是否對應到正確的版本。如果您鎖定的映像檔已被發布者更新,則策略評估會傳回不符合規範的狀態,表示您應該更新映像檔。

Docker Scout 也支援自動修復工作流程,以保持您的基礎映像檔為最新狀態。當新的映像檔摘要可用時,Docker Scout 可以自動在您的儲存庫上提出提取請求,以更新您的 Dockerfile 以使用最新版本。這比使用自動變更版本的標籤更好,因為您可以掌控一切,並且可以追蹤變更發生的時間和方式。

有關使用 Docker Scout 自動更新基礎映像檔的詳細資訊,請參閱修復

在 CI 中建置和測試您的映像檔

當您將變更簽入程式碼控制或建立提取請求時,請使用GitHub Actions 或其他 CI/CD 管線來自動建置和標記 Docker 映像檔並進行測試。

Dockerfile 指令

請遵循這些關於如何正確使用Dockerfile 指示來建立有效且可維護的 Dockerfile 的建議。

FROM

盡可能使用目前的官方映像檔作為映像檔的基礎。Docker 建議使用Alpine 映像檔,因為它受到嚴格控制且體積小(目前小於 6 MB),同時仍然是完整的 Linux 發行版。

有關 FROM 指示的詳細資訊,請參閱FROM 指示的 Dockerfile 參考

LABEL

您可以將標籤新增到映像檔中,以協助按專案組織映像檔、記錄授權資訊、協助自動化或其他原因。對於每個標籤,請新增以 LABEL 開頭的行,其中包含一或多個鍵值對。以下範例顯示不同的可接受格式。說明性註解包含在內聯中。

包含空格的字串必須用引號括起來,或必須逸出空格。內部引號字元 (") 也必須逸出。例如

# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""

一個映像檔可以有多個標籤。在 Docker 1.10 之前,建議將所有標籤合併到單個 LABEL 指令中,以防止建立額外的層。現在不再需要這樣做了,但仍然支援合併標籤。例如:

# Set multiple labels on one line
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"

以上範例也可以寫成:

# Set multiple labels at once, using line-continuation characters to break long lines
LABEL vendor=ACME\ Incorporated \
      com.example.is-beta= \
      com.example.is-production="" \
      com.example.version="0.0.1-beta" \
      com.example.release-date="2015-02-12"

有關可接受的標籤鍵和值的準則,請參閱了解物件標籤。有關查詢標籤的資訊,請參考管理物件上的標籤中與篩選相關的項目。另請參閱 Dockerfile 參考中的LABEL

RUN

將長或複雜的 RUN 陳述式用反斜線分隔成多行,以提高 Dockerfile 的可讀性、可理解性和可維護性。

例如,您可以使用 && 運算子串連指令,並使用跳脫字元將長指令斷成多行。

RUN apt-get update && apt-get install -y --no-install-recommends \
    package-bar \
    package-baz \
    package-foo

預設情況下,反斜線會跳脫換行字元,但您可以使用escape 指令來更改它。

您也可以使用 here 文件來執行多個指令,而無需使用管道運算子將它們串連起來

RUN <<EOF
apt-get update
apt-get install -y --no-install-recommends \
    package-bar \
    package-baz \
    package-foo
EOF

有關 RUN 的更多資訊,請參閱RUN 指令的 Dockerfile 參考

apt-get

在基於 Debian 的映像檔中,RUN 指令的一個常見用例是使用 apt-get 安裝軟體。由於 apt-get 會安裝套件,因此 RUN apt-get 指令有一些違反直覺的行為需要注意。

始終在同一個 RUN 陳述式中將 RUN apt-get updateapt-get install 結合使用。例如:

RUN apt-get update && apt-get install -y --no-install-recommends \
    package-bar \
    package-baz \
    package-foo

RUN 陳述式中單獨使用 apt-get update 會導致快取問題,並導致後續的 apt-get install 指令失敗。例如,這個問題會在以下 Dockerfile 中發生

# syntax=docker/dockerfile:1

FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends curl

建置映像檔後,所有層都會位於 Docker 快取中。假設您稍後修改 apt-get install,方法是新增一個額外的套件,如下列 Dockerfile 所示

# syntax=docker/dockerfile:1

FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends curl nginx

Docker 將初始指令和修改後的指令視為相同,並重複使用先前步驟中的快取。因此,不會執行 apt-get update,因為建置使用的是快取版本。由於未執行 apt-get update,因此您的建置可能會取得過時的 curlnginx 套件版本。

使用 RUN apt-get update && apt-get install -y --no-install-recommends 可確保您的 Dockerfile 安裝最新的套件版本,而無需進一步編碼或手動干預。這種技術稱為快取清除。您也可以透過指定套件版本來實現快取清除。這稱為版本鎖定。例如:

RUN apt-get update && apt-get install -y --no-install-recommends \
    package-bar \
    package-baz \
    package-foo=1.3.*

版本鎖定會強制建置擷取特定版本,而不考慮快取中的內容。這種技術還可以減少因所需套件的意外變更而導致的失敗。

以下是一個格式良好的 RUN 指令,示範了所有 apt-get 建議。

RUN apt-get update && apt-get install -y --no-install-recommends \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
    && rm -rf /var/lib/apt/lists/*

s3cmd 參數指定版本 1.1.*。如果映像檔先前使用的是舊版本,則指定新版本會導致 apt-get update 的快取清除,並確保安裝新版本。在每一行中列出套件也可以防止套件重複的錯誤。

此外,當您透過移除 /var/lib/apt/lists 來清除 apt 快取時,它會減少映像檔大小,因為 apt 快取不會儲存在層中。由於 RUN 陳述式以 apt-get update 開頭,因此套件快取總是在 apt-get install 之前重新整理。

官方 Debian 和 Ubuntu 映像檔會自動執行 apt-get clean,因此不需要明確呼叫。

使用管道

某些 RUN 指令依賴於使用管道字元 (|) 將一個指令的輸出管道傳輸到另一個指令的能力,如下例所示

RUN wget -O - https://some.site | wc -l > /number

Docker 使用 /bin/sh -c 直譯器執行這些指令,該直譯器僅評估管道中最後一個操作的結束代碼來判斷是否成功。在上面的範例中,只要 wc -l 指令成功,即使 wget 指令失敗,此建置步驟也會成功並產生新的映像檔。

如果您希望指令因管道中任何階段的錯誤而失敗,請在前面加上 set -o pipefail && ,以確保意外錯誤防止建置意外成功。例如:

RUN set -o pipefail && wget -O - https://some.site | wc -l > /number

**注意**

並非所有 shell 都支援 -o pipefail 選項。

在基於 Debian 的映像檔上的 dash shell 等情況下,請考慮使用 RUN 的 _exec_ 形式來明確選擇支援 pipefail 選項的 shell。例如:

RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]

CMD

CMD 指令應該用來執行映像檔中包含的軟體,以及任何參數。CMD 幾乎應該始終以 CMD ["executable", "param1", "param2"] 的形式使用。因此,如果映像檔用於服務,例如 Apache 和 Rails,您將運行類似 CMD ["apache2","-DFOREGROUND"] 的指令。實際上,建議任何基於服務的映像檔都使用這種形式的指令。

在大多數其他情況下,CMD 應該給予一個互動式 shell,例如 bash、python 和 perl。例如,CMD ["perl", "-de0"]CMD ["python"]CMD ["php", "-a"]。使用這種形式意味著當您執行類似 docker run -it python 的指令時,您將進入一個可用的 shell,隨時可以使用。除非您和您的預期使用者已經非常熟悉 ENTRYPOINT 的工作原理,否則很少會以 CMD ["param", "param"] 的方式與ENTRYPOINT結合使用 CMD

有關 CMD 的更多資訊,請參閱CMD 指令的 Dockerfile 參考

EXPOSE

EXPOSE 指令指示容器在其上偵聽連線的埠。因此,您應該為您的應用程式使用常見的傳統埠。例如,包含 Apache Web 伺服器的映像檔將使用 EXPOSE 80,而包含 MongoDB 的映像檔將使用 EXPOSE 27017,依此類推。

對於外部存取,您的使用者可以執行帶有旗標的 docker run,指示如何將指定的埠映射到他們選擇的埠。對於容器連結,Docker 提供了從接收容器返回到來源的路徑的環境變數(例如,MYSQL_PORT_3306_TCP)。

有關 EXPOSE 的更多資訊,請參閱EXPOSE 指令的 Dockerfile 參考

ENV

為了更容易執行新的軟體,您可以使用 ENV 更新容器安裝的軟體的 PATH 環境變數。例如,ENV PATH=/usr/local/nginx/bin:$PATH 可確保 CMD ["nginx"] 正常工作。

ENV 指令還可用於提供您要容器化的服務所需的特定環境變數,例如 Postgres 的 PGDATA

最後,ENV 還可以用於設定常用的版本號,以便更容易維護版本更新,如下例所示

ENV PG_MAJOR=9.3
ENV PG_VERSION=9.3.4
RUN curl -SL https://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgres &&
ENV PATH=/usr/local/postgres-$PG_MAJOR/bin:$PATH

類似於在程式中使用常數變數,而不是硬編碼值,這種方法允許您更改單個 ENV 指令來自動更新容器中軟體的版本。

每個 ENV 行都會建立一個新的中間層,就像 RUN 指令一樣。這意味著即使您在以後的層中取消設定環境變數,它仍然會保留在此層中,並且可以轉儲其值。您可以透過建立如下所示的 Dockerfile,然後建置它來測試這一點。

# syntax=docker/dockerfile:1
FROM alpine
ENV ADMIN_USER="mark"
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USER
$ docker run --rm test sh -c 'echo $ADMIN_USER'

mark

為了防止這種情況,並真正取消設定環境變數,請使用帶有 shell 指令的 RUN 指令,在單層中設定、使用和取消設定變數。您可以使用 ;&& 分隔您的指令。如果您使用第二種方法,並且其中一個指令失敗,則 docker build 也會失敗。這通常是一個好主意。將 \ 用作 Linux Dockerfile 的行繼續字元可以提高可讀性。您也可以將所有指令放入 shell 腳本中,並讓 RUN 指令只運行該 shell 腳本。

# syntax=docker/dockerfile:1
FROM alpine
RUN export ADMIN_USER="mark" \
    && echo $ADMIN_USER > ./mark \
    && unset ADMIN_USER
CMD sh
$ docker run --rm test sh -c 'echo $ADMIN_USER'

有關 ENV 的更多資訊,請參閱ENV 指令的 Dockerfile 參考

ADD 或 COPY

ADDCOPY 在功能上相似。COPY 支援將檔案從建置上下文多階段建置中的階段基本複製到容器中。ADD 支援從遠端 HTTPS 和 Git URL 擷取檔案的功能,並在從建置上下文新增檔案時自動解壓縮 tar 檔案。

在大多數情況下,您會想要使用 COPY 將檔案從一個階段複製到多階段建置中的另一個階段。如果您需要暫時將建置上下文中的檔案新增到容器中以執行 RUN 指令,您通常可以用繫結掛載代替 COPY 指令。例如,要暫時新增一個 requirements.txt 檔案以用於 RUN pip install 指令

RUN --mount=type=bind,source=requirements.txt,target=/tmp/requirements.txt \
    pip install --requirement /tmp/requirements.txt

COPY 相比,繫結掛載在將建置上下文中的檔案包含在容器中時效率更高。請注意,繫結掛載的檔案僅針對單個 RUN 指令暫時新增,並且不會保留在最終映像檔中。如果您需要在最終映像檔中包含建置上下文中的檔案,請使用 COPY

當您需要下載遠端構件作為建置的一部分時,ADD 指令最適合。ADD 比使用 wgettar 之類的工具手動新增檔案更好,因為它確保了更精確的建置快取。ADD 還內建支援遠端資源的校驗和驗證,以及一種用於從Git URL解析分支、標籤和子目錄的協定。

以下範例使用 ADD 指令下載 .NET 安裝程式。結合多階段建置,最終階段只會保留 .NET 執行環境,沒有中間檔案。

# syntax=docker/dockerfile:1

FROM scratch AS src
ARG DOTNET_VERSION=8.0.0-preview.6.23329.7
ADD --checksum=sha256:270d731bd08040c6a3228115de1f74b91cf441c584139ff8f8f6503447cebdbb \
    https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-runtime-$DOTNET_VERSION-linux-arm64.tar.gz /dotnet.tar.gz

FROM mcr.microsoft.com/dotnet/runtime-deps:8.0.0-preview.6-bookworm-slim-arm64v8 AS installer

# Retrieve .NET Runtime
RUN --mount=from=src,target=/src <<EOF
mkdir -p /dotnet
tar -oxzf /src/dotnet.tar.gz -C /dotnet
EOF

FROM mcr.microsoft.com/dotnet/runtime-deps:8.0.0-preview.6-bookworm-slim-arm64v8

COPY --from=installer /dotnet /usr/share/dotnet
RUN ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet

關於 ADDCOPY 的更多資訊,請參閱以下內容

ENTRYPOINT

ENTRYPOINT 的最佳用途是設定映像檔的主要指令,讓映像檔可以像執行該指令一樣運行,然後使用 CMD 作為預設旗標。

以下是命令列工具 s3cmd 的映像檔範例

ENTRYPOINT ["s3cmd"]
CMD ["--help"]

您可以使用以下指令來執行映像檔並顯示指令的說明

$ docker run s3cmd

或者,您可以使用正確的參數來執行指令,如下例所示

$ docker run s3cmd ls s3://mybucket

這很有用,因為映像檔名稱可以兼作二進制檔的參考,如上述指令所示。

ENTRYPOINT 指令也可以與輔助腳本結合使用,使其功能與上述指令類似,即使啟動工具可能需要多個步驟。

例如,Postgres 官方映像檔 使用以下腳本作為其 ENTRYPOINT

#!/bin/bash
set -e

if [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"

    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi

    exec gosu postgres "$@"
fi

exec "$@"

此腳本使用 exec Bash 指令,讓最終運行的應用程式成為容器的 PID 1。這允許應用程式接收任何發送到容器的 Unix 訊號。如需更多資訊,請參閱 ENTRYPOINT 參考文件

在以下範例中,一個輔助腳本被複製到容器中,並在容器啟動時透過 ENTRYPOINT 運行

COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["postgres"]

此腳本允許您以多種方式與 Postgres 互動。

它可以簡單地啟動 Postgres

$ docker run postgres

或者,您可以使用它來運行 Postgres 並將參數傳遞給伺服器

$ docker run postgres postgres --help

最後,您可以使用它來啟動完全不同的工具,例如 Bash

$ docker run --rm -it postgres bash

關於 ENTRYPOINT 的更多資訊,請參閱 ENTRYPOINT 指令的 Dockerfile 參考文件

VOLUME

您應該使用 VOLUME 指令來公開任何資料庫儲存區域、配置儲存空間,或 Docker 容器建立的檔案和資料夾。強烈建議您對映像檔中任何可變或使用者可維護的部分使用 VOLUME

關於 VOLUME 的更多資訊,請參閱 VOLUME 指令的 Dockerfile 參考文件

USER

如果服務可以在沒有權限的情況下運行,請使用 USER 切換到非 root 使用者。首先在 Dockerfile 中使用類似以下範例的方式建立使用者和群組

RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres

**注意**

考慮明確的 UID/GID。

映像檔中的使用者和群組會被分配一個非確定性的 UID/GID,也就是說,無論映像檔如何重建,都會分配「下一個」UID/GID。因此,如果這很重要,您應該分配一個明確的 UID/GID。

**注意**

由於 Go archive/tar 套件在處理稀疏檔案時存在一個 未解決的錯誤“gosu”

最後,為了減少層數和複雜性,避免頻繁地來回切換 USER

關於 USER 的更多資訊,請參閱 USER 指令的 Dockerfile 參考文件

WORKDIR

為了清晰和可靠性,您應該始終為 WORKDIR 使用絕對路徑。此外,您應該使用 WORKDIR,而不是使用像 RUN cd … && do-something 這樣難以閱讀、排除故障和維護的指令。

關於 WORKDIR 的更多資訊,請參閱 WORKDIR 指令的 Dockerfile 參考文件

ONBUILD

ONBUILD 指令在當前 Dockerfile 建置完成後執行。ONBUILD 會在任何從當前映像檔衍生的子映像檔中執行。將 ONBUILD 指令視為父 Dockerfile 給子 Dockerfile 的指令。

Docker 建置會在子 Dockerfile 中的任何指令之前執行 ONBUILD 指令。

ONBUILD 對於將從指定映像檔建置的映像檔很有用。例如,您會將 ONBUILD 用於在 Dockerfile 中建置以該語言編寫的任意使用者軟體的語言堆疊映像檔,如您在 Ruby 的 ONBUILD 變體ONBUILD 指令的 Dockerfile 參考文件