Tối ưu docker image
tl;dr
- tối ưu
.dockerignore
. - dùng minimal base image, ví dụ
alpine
,slim
,distroless
. - giảm số lượng command / layer.
- install production dependency thôi.
- dùng remote build system, ví dụ
nx
hayturborepo
, để cache lại build. - sử dụng multistage build.
mục đích và ngữ cảnh
- mục đích: tối ưu dung lượng và thời gian build docker image để tăng hiệu suất triển khai.
- ngữ cảnh: build một full-stack JavaScript web app dùng React và Nest.js.
gốc
FROM node:18
EXPOSE 8081
WORKDIR /web
# copy source files
COPY package.json ./
COPY package-lock.json ./
COPY turbo.json ./
COPY packages ./packages
COPY apps/client ./apps/client
COPY apps/server ./apps/server
# install dependencies
RUN npm ci
# build
RUN npm run build
# run
CMD ["npm", "run", "serve", "-w", "server"]
tối ưu .dockerignore
- ignore hết mấy file không cần thiết khỏi docker image như
README
, ci, git, logs, env, dist, …
.env
logs
*.log
npm-debug.log*
docs
*.md
coverage
node_modules
.npm
dist
.git
.DS_Store
dùng minimal base image
- dùng image nhẹ làm base image. image này được lược bỏ tối đa tính năng không cần thiết để tối ưu dung lượng.
- một vài minimal base image tiêu biểu là
alpine
,slim
vàdistroless
. - trong 3 base images trên, thì
alpine
là image thông dụng nhất, còn nhẹ nhất thì làdistroless
. - vì bị lược bỏ tối đa tính năng nên bạn phải tự cài tay những thư việc bạn muốn. ví dụ, sử dụng
distroless
image, phải tự cài thêmnpm
nếu muốn cài thêm thư viện. - vào việc. ở bước này mình sẽ dùng
node:alpine
(alpine
cài thêmnode.js
).
FROM node:18-alpine
- một lưu ý nhỏ là
Alpine
không phảiUbuntu
, nên các câu lệnh có thể khác biệt. điển hình là câu lệnh quản lý thư viện, trongAlpine
làapk
còn trongUbuntu
làapt-get
.
giảm số lượng câu lệnh / layer
- hiểu đơn giản thế này, docker image được tạo từ nhiều layer khác nhau. mỗi câu lệnh (
RUN
,COPY
,FROM
,ADD
, …) tạo thêm một layer cho docker image, càng nhiều câu lệnh thì docker image càng nặng. - do đó, nên gộp chung các câu lệnh để giảm số lượng layer.
- vào việc. ở bước này mình sẽ gộp các cậu lệnh
COPY
ở trên thành 2 câu lệnh.
COPY package*.json turbo.json ./
COPY packages apps ./
install production dependency thôi
- thay vì install tất cả dependency, bạn chỉ nên install các dependency giúp app build và chạy.
- vào việc. ở bước này mình thêm
omit=dev
vào câu lệnhnpm ci
.
# install dependencies
RUN npm ci --omit=dev
dùng remote build system
- cache lại output sau mỗi lần build, nếu ở lần build tiếp theo code không đổi thì lấy từ cache ra thay vì phải build lại.
- vào việc. ở bước này mình dùng
turborepo
, bạn có thể dùngnx
cái gì khác thay thế. lưu ý, bạn cần setup file settingturbo.json
trước.
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"dev": {
"cache": false,
"persistent": true
},
"build": {
"cache": true,
"persistent": false,
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}
{
"scripts": {
"build": "turbo run build --remote-only"
}
}
ENV TURBO_TEAM=xxx
ARG TURBO_TOKEN
ENV TURBO_TOKEN=$TURBO_TOKEN
sử dụng multistage build
- tách stage build và stage run riêng. mục đích là để loại bỏ những layer chỉ cần thiết ở stage build mà không cần thiết ở stage run để giảm dung lượng image.
- vào việc. ở bước này mình tách ra thành 2 stage run và build. stage run chỉ copy thư mục
dist
từ stage build. tiện tay, mình cũng dùng imagedistroless
thay chonode:18-alpine
.
# BUILD
FROM node:20 AS build
WORKDIR /app
...
# build
RUN npm run build
# RUN
FROM gcr.io/distroless/nodejs20-debian11
EXPOSE 8081
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app /app
CMD ["apps/server/index.js"]
nếu bạn vẫn rảnh
bạn có thể dùng depot.dev để giảm thời gian build. bên này quảng cáo giảm ~ 20 lần thời gian build trong một số trường hợp. nếu rảnh bạn có thể dùng thử và chia sẻ trải nghiệm giúp mình, chứ mình chưa dùng 🙈.
trong trường hợp mà bạn vẫn muốn tối ưu docker image lên một tầm cao mới (hoặc bạn quá rảnh), bạn có thể cân nhắc dùng một vài tool để audit docker image, ví dụ Dive, Docker Slim hay Docker Squash. mấy tool này mình cũng chưa dùng đâu, ai dùng rồi thì lại chia sẻ trải nghiệm nhé.
kết luận
- trên đây là một vài kinh nghiệm để tối ưu docker image mình tự đúc rút (cũng như copy trên mạng). lý thuyết vẫn chỉ là lý thuyết, bạn cần áp dụng chúng một cách hợp lý cho dự án của mình để đạt kết quả tốt.