使用建置快取

說明

請參考您為 getting-started 應用程式所建立的 Dockerfile。

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

當您執行 docker build 指令來建立新的映像檔時,Docker 會依照指定的順序執行 Dockerfile 中的每一道指令,並為每個指令建立一個層(layer)。對於每一道指令,Docker 都會檢查是否可以重複使用先前建置的結果。如果發現您之前已經執行過類似的指令,Docker 就不需要重新執行,而是會直接使用快取的結果。透過這種方式,您的建置過程會變得更快、更有效率,節省您寶貴的時間與資源。

有效使用建置快取可以透過重複使用先前建置的結果並跳過不必要的工作來達成更快的建置速度。為了最大化快取的使用率,並避免消耗資源且耗時的重新建置,了解快取失效(cache invalidation)的機制非常重要。以下列舉幾種可能導致快取失效的情況:

  • RUN 指令的任何更動都會導致該層失效。如果 Dockerfile 中的 RUN 指令有任何修改,Docker 會偵測到該變動並使建置快取失效。

  • 透過 COPYADD 指令複製到映像檔中的檔案有任何變動。Docker 會監控專案目錄內檔案的任何更動。無論是內容的改變,還是權限等屬性的變更,Docker 都會將這些修改視為觸發快取失效的因素。

  • 一旦某一層失效,後續所有的層也會隨之失效。如果任何前置層(包含基礎映像檔或中間層)因為變更而失效,Docker 會確保所有依賴於它的後續層也一併失效。這能確保建置過程保持同步並防止不一致。

在編寫或編輯 Dockerfile 時,請留意不必要的快取未命中(cache misses),以確保建置過程盡可能快速且高效。

試試看

在本實作指南中,您將學習如何為 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 .
    

    只要指令和上下文(context)保持不變,首次之後的後續建置會因為快取機制而變得更快。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 的後設資料(metadata)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 檔案進行修改(例如將標題改為 "The Awesome Todo App")。

  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 建置快取,您現在已準備好學習「多階段建置」(Multi-stage builds)。

多階段建置
© . This site is unofficial and not affiliated with Kubernetes or Docker Inc.