Giới thiệu

Dockerfile đa giai đoạn (multi‑stage build) là một kỹ thuật mạnh mẽ giúp bạn tách riêng quá trình biên dịch và tạo ra môi trường chạy thực tế. Khi áp dụng đúng, kích thước image cuối cùng có thể giảm tới 80% so với cách viết truyền thống, đồng thời loại bỏ các công cụ không cần thiết, giảm bề mặt tấn công và tăng tốc độ triển khai.

Tại sao nên sử dụng multi‑stage build

Trong môi trường production, mỗi megabyte đều có giá trị. Image lớn không chỉ tốn thời gian tải xuống mà còn tiêu tốn nhiều tài nguyên lưu trữ trên registry. Multi‑stage build cho phép bạn:

  • Giảm kích thước image bằng cách chỉ sao chép những file cần thiết vào stage cuối cùng.
  • Tách biệt môi trường build và runtime – các công cụ biên dịch, package manager chỉ tồn tại trong stage đầu, không xuất hiện trong container chạy thực tế.
  • Quản lý phụ thuộc một cách rõ ràng – mỗi stage có thể dựa trên một base image phù hợp với mục đích của nó.

Giảm kích thước image

Ví dụ, khi xây dựng một ứng dụng Node.js, bạn có thể dùng node:18-alpine làm stage build để cài đặt các package và biên dịch mã nguồn, sau đó chuyển sang node:18-alpine nhẹ hơn (hoặc scratch) để chỉ chứa file distnode_modules đã được tối ưu.

Tách biệt môi trường build và runtime

Việc tách biệt này giúp giảm bề mặt tấn công vì các công cụ như gcc, make không còn tồn tại trong container production. Ngoài ra, khi có lỗ hổng bảo mật trong một công cụ biên dịch, bạn không cần cập nhật image runtime.

Cách viết Dockerfile đa giai đoạn chuẩn

Dưới đây là một Dockerfile mẫu cho một ứng dụng Node.js sử dụng TypeScript. Các stage được đặt tên rõ ràng và chỉ sao chép những gì thực sự cần thiết.

# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build

# Stage 2: Production runtime
FROM node:18-alpine AS runtime
WORKDIR /app
# Chỉ sao chép file đã build và các dependency production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production
EXPOSE 3000
CMD ["node", "dist/index.js"]

Trong ví dụ trên, npm ci được chạy ở cả hai stage, nhưng ở stage cuối chỉ cài đặt các dependency production, giúp giảm kích thước cuối cùng.

Kỹ thuật tối ưu thêm

Một số mẹo bổ sung để giảm dung lượng image:

  1. Sử dụng --no-cache khi cài package: Tránh lưu lại các file tạm thời trong layer cuối.
  2. Loại bỏ file .git, docs, test bằng .dockerignore để chúng không được sao chép vào bất kỳ stage nào.
  3. Chọn base image nhẹ: alpine thường nhỏ hơn debian, nhưng cần kiểm tra tính tương thích của các binary.
  4. Sử dụng scratch cho các ứng dụng Go hoặc Rust đã biên dịch tĩnh, vì không có bất kỳ layer nào khác.
  5. Minify static assets (HTML, CSS, JS) trước khi copy vào stage runtime.

Kiểm tra kích thước và layer

Sau khi build, bạn có thể dùng lệnh docker image ls để xem kích thước tổng thể, và docker history <image_name> để kiểm tra số lượng layer. Nếu có layer không cần thiết, hãy hợp nhất chúng bằng cách dùng && trong một lệnh RUN duy nhất.

Kết luận

Áp dụng multi‑stage build không chỉ giúp giảm kích thước image mà còn nâng cao bảo mật và hiệu suất triển khai. Khi đã nắm vững các nguyên tắc trên, bạn có thể tự tin tối ưu bất kỳ dự án nào, từ Node.js, Python tới Java.

Tham khảo khóa học "Làm chủ Docker từ cơ bản đến nâng cao" tại đây