實測有效!手把手帶你將 Docker Image 體積減少 90%

林鼎淵
Dean Lin
Published in
9 min readSep 1, 2022

--

Docker Image 體積越大,那部署要花的時間就越長;假如每個版本都有好幾 GB,那並不是一個理想的狀態;因此筆者開始動手實作,想看看到底能將 Docker Image 的體積縮小多少!

大綱ㄧ、先初始化一個簡易的 Node.js 專案二、撰寫 Dockefile,了解優化前體積有多大三、使用 Node.js 的 Alpine 版本四、正式環境下,不需要安裝 devDependencies 的套件五、如果我們只使用最基礎的 Alpine,然後 Node.js 自己安裝呢?六、採用多階段構建,取用上個階段產生的結果七、使用 Distroless 讓正式環境更加安全

ㄧ、先初始化一個簡易的 Node.js 專案

# 建立資料夾
mkdir docker-test
cd docker-test

# 初始化應用
npm init

# 安裝 express
npm install express --save

初始化後的 package.json 大概會長這樣(scripts 的 start 筆者有微調):

{
"name": "docker-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.18.1"
}
}

接著我們新增一個「index.js」的檔案,並貼上如下程式:

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
res.send('Hello World!')
})

app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

為了實測不同情境打包的影響,我們安裝一個儲存在「devDependencies」的套件:

npm install eslint -save-dev

二、撰寫 Dockefile,了解優化前體積有多大

這邊我們就先用最簡易的方式寫 Dockerfile:

FROM node# 運行的資料夾
WORKDIR /usr/src/app
# 移動所需檔案
COPY package.json index.js ./
# 安裝套件
RUN npm install
# 提供服務的接口
EXPOSE 3000
CMD ["npm", "start"]

接下來輸入 docker build -t docker-test . 就可以建立 Docker Image(docker-test 是名稱)。

然後輸入 docker images ,確認剛剛建立的 Docker Image 是否存在;從下圖我們可以看到優化前的 Image 大小高達 1.01GB

三、使用 Node.js 的 Alpine 版本

Node.js Alpine 版本的 Image 體積會遠小於完整的 Node.js Image,現在我們修改一下 Dockerfile:

# 使用 Alpine 版本 
FROM node:alpine
WORKDIR /usr/src/appCOPY package.json index.js ./RUN npm installEXPOSE 3000CMD ["npm", "start"]

輸入 docker build -t docker-test-alpine . 來建立 Image,完成後輸入 docker images 看看 build 出來的 Image 體積是否改變。

從上圖我們可以看到,Alpine 的版本讓它的體積從 1.01GB 下降到了 189MB,整整少了 812MB!

四、正式環境下,不需要安裝 devDependencies 的套件

通常一個專案會安裝一些 devDependencies 的套件,但這些套件只需要在開發環境中輔助使用,在正式環境下並沒有安裝的必要。

我們調整一下 Dockerfile 安裝套件的指令:

FROM node:alpine

WORKDIR /usr/src/app

COPY package.json index.js ./

# 只安裝 dependencies 的套件
RUN npm install --production

EXPOSE 3000

CMD ["npm", "start"]

在上圖我們可以看到體積又變小了,果然還有優化的空間(少了 12MB)。

五、如果我們只使用最基礎的 Alpine,然後 Node.js 自己安裝呢?

剛剛我們使用的是 Node.js 的 Alpine 版本,如果更極端一點,只使用最基礎的 Alpine,然後自己額外安裝 Node.js 會有什麼樣的結果呢?

# 使用最基礎的 Alpine
FROM alpine:latest

# 自己安裝 Node.js & npm
RUN apk add --no-cache --update nodejs npm

WORKDIR /usr/src/app

COPY package.json index.js ./

RUN npm install --production

EXPOSE 3000

CMD ["npm", "start"]

沒想到這個操作把整包 Image 的體積壓的更低了(從 177MB 降到 64.7MB)。

六、採用多階段構建,取用上個階段產生的結果

Docker Image 你可以先想想成疊疊樂,在這之中多執行一次「RUN」就會多疊一層;而在 Docker 的世界中可以允許有多個「FROM」,但只會取用最後一個「FROM」所建立的 Image。

在了解上面的概念後,我們就可以把「安裝」的步驟放在第一個「FROM」裡面執行,然後第二個 FROM 就只是單純地把第一層安裝好的結果搬過去即可,用 Dockerfile 實現會長這樣:

FROM alpine:latest AS builder
RUN apk add --no-cache --update nodejs npm
WORKDIR /usr/src/app
COPY package.json index.js ./
# 先完成安裝
RUN npm install --production
FROM alpine:latest
RUN apk add --no-cache --update nodejs npm
WORKDIR /usr/src/app
# 把安裝好的全部移過去
COPY --from=builder /usr/src/app .
EXPOSE 3000
CMD ["npm", "start"]

經過這麼多的努力後,我們的 Docker Image 也順利從 1.01GB 下降到 58.9MB,減少了超過 94% 的體積,並且可以明顯感受到 build Image 的速度上升不少。

七、使用 Distroless 讓正式環境更加安全

儘管上面我們已經使用 Alpine 讓 Docker Image 變得這麼小,但在文章的最後我想再提供讀者另一個選擇, 那就是「Distroless」!

先讓我們用它來 Build 一個 Docker Image。

FROM node AS builder
WORKDIR /usr/src/app
COPY package.json index.js ./
RUN npm install --production
# 改成用 Distroless
FROM gcr.io/distroless/nodejs
WORKDIR /usr/src/app
COPY --from=builder /usr/src/app .
EXPOSE 3000
CMD ["index.js"]

如果單純從結果來看,它在體積上(162MB)並沒有什麼優勢,但如果你嘗試用 Shell 打開它,會發現 Shell 根本不存在!換而言之,它的安全性相對更高,可以考慮使用它來做正式環境的部署。

以上就是筆者優化 Docker Image 體積的步驟啦!如果文中有表達不清晰、錯誤的部分再煩請告知,感謝您的閱讀。

參考資源:Node.js docker 鏡像體積優化實踐三個技巧,將Docker鏡像體積減小90%

▶︎ 如果這篇文章有幫助到你1. 可以點擊下方「Follow」來追蹤我~
2. 可以對文章拍手讓我知道 👏🏻
你們的追蹤與鼓勵是我繼續寫作的動力 🙏🏼▶︎ 如果你對工程師的職涯感到迷茫1. 也許我在iT邦幫忙發表的系列文可以給你不一樣的觀點 💡
2. 也歡迎您到書局選購支持,透過豐富的案例來重新檢視自己的職涯

--

--

林鼎淵
Dean Lin

職涯中培育過多名工程師,🧰 目前在外商公司擔任 Software Specialist |✍️ 我專注寫 (1)最新技術 (2)團隊合作 (3)工程師職涯的文章,出版過 5 本專業書籍|👏🏻 如果對這些主題感興趣,歡迎點擊「Follow」來關注我~