Skip to content

Thực hành: Multi-stage Builds

🎯 Mục tiêu

🎯 Sau bài thực hành này, bạn sẽ:

  • Hiểu builder pattern và tách build stage khỏi runtime stage
  • Giảm kích thước image từ hàng trăm MB xuống chục MB
  • Biết khi nào nên và không nên dùng multi-stage
  • Tối ưu layer caching trong multi-stage Dockerfile

Phần 1: Trắc nghiệm

🧠 Quiz

Câu 1: Multi-stage build giúp giảm kích thước image bằng cách nào?

  • [ ] A) Nén toàn bộ image thành file zip
  • [ ] B) Xóa tất cả layer trung gian sau khi build xong
  • [x] C) Chỉ copy artifact cần thiết từ build stage sang runtime stage nhỏ gọn
  • [ ] D) Tự động loại bỏ file trùng lặp giữa các layer

💡 Giải thích: Multi-stage cho phép bạn dùng image lớn (có compiler, build tools) ở stage đầu, rồi chỉ COPY binary/artifact sang image nhỏ (alpine, distroless) ở stage cuối. Build tools không tồn tại trong final image.

🧠 Quiz

Câu 2: Trong Dockerfile multi-stage, COPY --from=builder có ý nghĩa gì?

  • [ ] A) Copy file từ host machine vào container
  • [x] B) Copy file từ stage có tên "builder" sang stage hiện tại
  • [ ] C) Copy file từ Docker Hub về local
  • [ ] D) Copy và ghi đè file đã tồn tại trong image

💡 Giải thích: COPY --from=builder /app/dist ./dist lấy file từ stage được đặt tên qua FROM node:20 AS builder. Bạn cũng có thể dùng số thứ tự stage: COPY --from=0.

🧠 Quiz

Câu 3: Khi nào KHÔNG nên dùng multi-stage build?

  • [ ] A) Khi build Go binary
  • [ ] B) Khi build React/Vue frontend
  • [x] C) Khi image chỉ cần interpreted language và dependencies nhẹ
  • [ ] D) Khi cần tách dev dependencies khỏi production

💡 Giải thích: Nếu app chỉ cần Python + vài package nhỏ, multi-stage không mang lại lợi ích đáng kể — thêm phức tạp mà image không giảm nhiều. Multi-stage tỏa sáng khi có bước compile (Go, Rust, TypeScript) hoặc build tool nặng (webpack, gcc).

Phần 2: Sắp xếp Multi-stage Dockerfile

🧩 Parsons Problem

Bài 1: Multi-stage cho React App

Sắp xếp instruction đúng thứ tự cho Dockerfile 2 stage:

  1. FROM node:20-alpine AS builder
  2. WORKDIR /app
  3. COPY package.json package-lock.json ./
  4. RUN npm ci
  5. COPY . .
  6. RUN npm run build
  7. FROM nginx:alpine
  8. COPY --from=builder /app/dist /usr/share/nginx/html
  9. EXPOSE 80
  10. CMD ["nginx", "-g", "daemon off;"]

🧩 Parsons Problem

Bài 2: Multi-stage cho Go API

Sắp xếp Dockerfile tối ưu cho Go binary:

  1. FROM golang:1.22-alpine AS builder
  2. WORKDIR /src
  3. COPY go.mod go.sum ./
  4. RUN go mod download
  5. COPY . .
  6. RUN CGO_ENABLED=0 go build -o /app/server ./cmd/server
  7. FROM scratch
  8. COPY --from=builder /app/server /server
  9. EXPOSE 8080
  10. ENTRYPOINT ["/server"]

Phần 3: Tìm lỗi — Image phình to

🐛 Spot-the-Bug

Dockerfile multi-stage bị sai

dockerfile
FROM node:20 AS builder
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build

FROM node:20
WORKDIR /app
COPY --from=builder /app .
RUN npm ci --only=production
EXPOSE 3000
CMD ["node", "dist/server.js"]

Câu hỏi: Image cuối vẫn rất lớn (~900MB). Tại sao?

Đáp án:

  1. Runtime stage dùng node:20 (~900MB) — nên dùng node:20-alpine (~130MB).
  2. COPY toàn bộ /app gồm cả source, dev deps. Chỉ nên COPY /app/distpackage.json.
  3. Chạy npm ci lại — nên COPY node_modules production từ builder hoặc install riêng.