使用構建快取

解釋

請看您為入門應用建立的以下 Dockerfile。

FROM node:22-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "./src/index.js"]

當您執行 docker build 命令來建立新映象時,Docker 會執行您 Dockerfile 中的每條指令,為每個命令建立一個層,並按指定的順序進行。對於每條指令,Docker 會檢查是否可以重用先前構建中的指令。如果發現之前已經執行過類似的指令,Docker 就無需重做。相反,它會使用快取的結果。這樣,您的構建過程會變得更快、更高效,從而節省寶貴的時間和資源。

有效利用構建快取,您可以透過重用先前構建的結果並跳過不必要的工作來實現更快的構建。為了最大化快取利用率並避免資源密集且耗時的重新構建,理解快取失效的工作原理非常重要。以下是一些可能導致快取失效的情況示例:

  • RUN 指令的命令進行任何更改都會使該層失效。如果 Dockerfile 中的 RUN 命令有任何修改,Docker 會檢測到更改並使構建快取失效。

  • 使用 COPYADD 指令複製到映象中的檔案發生任何更改。Docker 會監視您專案目錄中檔案的任何變動。無論是內容更改還是許可權等屬性的更改,Docker 都會將這些修改視為觸發快取失效的因素。

  • 一旦某一層失效,其後所有層也會失效。如果任何先前的層,包括基礎映象或中間層,因更改而失效,Docker 會確保依賴於它的後續層也失效。這可以保持構建過程的同步並防止不一致。

在編寫或編輯 Dockerfile 時,請留意不必要的快取未命中,以確保構建儘可能快速高效地執行。

試一試

在這個實踐指南中,您將學習如何為一個 Node.js 應用程式有效地使用 Docker 構建快取。

構建應用程式

  1. 下載並安裝 Docker Desktop。

  2. 開啟一個終端並克隆這個示例應用程式

    $ git clone https://github.com/dockersamples/todo-list-app
    
  3. 導航到 todo-list-app 目錄

    $ cd todo-list-app
    

    在這個目錄中,您會找到一個名為 Dockerfile 的檔案,其內容如下:

    FROM node:22-alpine
    WORKDIR /app
    COPY . .
    RUN yarn install --production
    EXPOSE 3000
    CMD ["node", "./src/index.js"]
  4. 執行以下命令來構建 Docker 映象

    $ docker build .
    

    以下是構建過程的結果:

    [+] Building 20.0s (10/10) FINISHED
    

    第一行顯示整個構建過程耗時 20.0 秒。第一次構建可能需要一些時間,因為它需要安裝依賴項。

  5. 在不作任何更改的情況下重新構建。

    現在,在不更改原始碼或 Dockerfile 的情況下,重新執行 docker build 命令,如下所示:

    $ docker build .
    

    由於快取機制,只要命令和上下文保持不變,首次構建後的後續構建會更快。Docker 會快取構建過程中生成的中間層。當您在不更改 Dockerfile 或原始碼的情況下重新構建映象時,Docker 可以重用快取的層,從而顯著加快構建過程。

    [+] Building 1.0s (9/9) FINISHED                                                                            docker:desktop-linux
     => [internal] load build definition from Dockerfile                                                                        0.0s
     => => transferring dockerfile: 187B                                                                                        0.0s
     ...
     => [internal] load build context                                                                                           0.0s
     => => transferring context: 8.16kB                                                                                         0.0s
     => CACHED [2/4] WORKDIR /app                                                                                               0.0s
     => CACHED [3/4] COPY . .                                                                                                   0.0s
     => CACHED [4/4] RUN yarn install --production                                                                              0.0s
     => exporting to image                                                                                                      0.0s
     => => exporting layers                                                                                                     0.0s
     => => exporting manifest
    

    後續構建透過利用快取層僅用 1.0 秒就完成了。無需重複像安裝依賴項這樣耗時的步驟。

    步驟描述耗時(第一次執行)耗時(第二次執行)
    1從 Dockerfile 載入構建定義0.0 秒0.0 秒
    2載入 docker.io/library/node:22-alpine 的元資料2.7 秒0.9 秒
    3載入 .dockerignore0.0 秒0.0 秒
    4載入構建上下文

    (上下文大小:4.60MB)

    0.1 秒0.0 秒
    5設定工作目錄 (WORKDIR)0.1 秒0.0 秒
    6將原生代碼複製到容器中0.0 秒0.0 秒
    7執行 yarn install --production10.0 秒0.0 秒
    8匯出層2.2 秒0.0 秒
    9匯出最終映象3.0 秒0.0 秒

    回到 docker image history 的輸出,您會看到 Dockerfile 中的每個命令都成為映象中的一個新層。您可能還記得,當您對映象進行更改時,必須重新安裝 yarn 依賴項。有沒有辦法解決這個問題?每次構建時都重新安裝相同的依賴項,這似乎不太合理,對吧?

    為了解決這個問題,請重構您的 Dockerfile,以便依賴項快取保持有效,除非它確實需要被無效化。對於基於 Node 的應用程式,依賴項在 package.json 檔案中定義。如果該檔案發生變化,您會希望重新安裝依賴項,但如果檔案未變,則使用快取的依賴項。所以,首先只複製那個檔案,然後安裝依賴項,最後再複製其他所有內容。這樣,只有在 package.json 檔案發生更改時,您才需要重新建立 yarn 依賴項。

  6. 更新 Dockerfile,先複製 package.json 檔案,安裝依賴項,然後再複製其他所有內容。

    FROM node:22-alpine
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production 
    COPY . . 
    EXPOSE 3000
    CMD ["node", "src/index.js"]
  7. 在與 Dockerfile 相同的資料夾中建立一個名為 .dockerignore 的檔案,其內容如下。

    node_modules
  8. 構建新映象

    $ docker build .
    

    然後您將看到類似以下的輸出:

    [+] Building 16.1s (10/10) FINISHED
    => [internal] load build definition from Dockerfile                                               0.0s
    => => transferring dockerfile: 175B                                                               0.0s
    => [internal] load .dockerignore                                                                  0.0s
    => => transferring context: 2B                                                                    0.0s
    => [internal] load metadata for docker.io/library/node:22-alpine                                  0.0s
    => [internal] load build context                                                                  0.8s
    => => transferring context: 53.37MB                                                               0.8s
    => [1/5] FROM docker.io/library/node:22-alpine                                                    0.0s
    => CACHED [2/5] WORKDIR /app                                                                      0.0s
    => [3/5] COPY package.json yarn.lock ./                                                           0.2s
    => [4/5] RUN yarn install --production                                                           14.0s
    => [5/5] COPY . .                                                                                 0.5s
    => exporting to image                                                                             0.6s
    => => exporting layers                                                                            0.6s
    => => writing image     
    sha256:d6f819013566c54c50124ed94d5e66c452325327217f4f04399b45f94e37d25        0.0s
    => => naming to docker.io/library/node-app:2.0                                                 0.0s
    

    您會看到所有層都已重建。這完全正常,因為您對 Dockerfile 做了相當大的改動。

  9. 現在,對 src/static/index.html 檔案做一個更改(比如將標題改為“超棒的待辦事項應用”)。

  10. 構建 Docker 映象。這一次,您的輸出應該會有些不同。

    $ docker build -t node-app:3.0 .
    

    然後您將看到類似以下的輸出:

    [+] Building 1.2s (10/10) FINISHED 
    => [internal] load build definition from Dockerfile                                               0.0s
    => => transferring dockerfile: 37B                                                                0.0s
    => [internal] load .dockerignore                                                                  0.0s
    => => transferring context: 2B                                                                    0.0s
    => [internal] load metadata for docker.io/library/node:22-alpine                                  0.0s 
    => [internal] load build context                                                                  0.2s
    => => transferring context: 450.43kB                                                              0.2s
    => [1/5] FROM docker.io/library/node:22-alpine                                                    0.0s
    => CACHED [2/5] WORKDIR /app                                                                      0.0s
    => CACHED [3/5] COPY package.json yarn.lock ./                                                    0.0s
    => CACHED [4/5] RUN yarn install --production                                                     0.0s
    => [5/5] COPY . .                                                                                 0.5s 
    => exporting to image                                                                             0.3s
    => => exporting layers                                                                            0.3s
    => => writing image     
    sha256:91790c87bcb096a83c2bd4eb512bc8b134c757cda0bdee4038187f98148e2eda       0.0s
    => => naming to docker.io/library/node-app:3.0                                                 0.0s
    

    首先,您應該注意到構建速度快了很多。您會看到有幾個步驟正在使用之前快取的層。這是個好訊息;您正在使用構建快取。推送和拉取這個映象及其更新也會快得多。

透過遵循這些最佳化技巧,您可以使您的 Docker 構建更快、更高效,從而加快迭代週期並提高開發生產力。

其他資源

後續步驟

既然您已經瞭解瞭如何有效地使用 Docker 構建快取,現在可以學習多階段構建了。

多階段構建