使用構建快取

解釋

考慮您為 入門 應用程式建立的以下 Dockerfile。

FROM node:20-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:20-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. 在不進行更改的情況下重新構建。

    現在,重新執行 docker build 命令,而不對原始碼或 Dockerfile 進行任何更改,如所示

    $ 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:20-alpine 的元資料2.7 秒0.9 秒
    3載入 .dockerignore0.0 秒0.0 秒
    4載入構建上下文

    (上下文大小:4.60 MB)

    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:20-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:21-alpine                                  0.0s
    => [internal] load build context                                                                  0.8s
    => => transferring context: 53.37MB                                                               0.8s
    => [1/5] FROM docker.io/library/node:21-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:21-alpine                                  0.0s 
    => [internal] load build context                                                                  0.2s
    => => transferring context: 450.43kB                                                              0.2s
    => [1/5] FROM docker.io/library/node:21-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 構建快取,您就可以開始學習多階段構建了。