Docker Multi-stage Build: Giảm kích thước image xuống 20MB từ 1GB

Docker Multi-stage Build: Giảm kích thước image xuống 20MB từ 1GB

Docker multi-stage build là kỹ thuật chia Dockerfile thành nhiều giai đoạn (stage), mỗi stage dùng base image khác nhau và chỉ copy artifact cần thiết sang stage cuối. Kết quả: image production siêu nhẹ — chỉ chứa binary/app runtime, không có build tools, source code, test suite hay dependency dev. Một Go/Rust app thường giảm từ 1GB+ xuống 20-50MB (scratch/distroless) hoặc 200-300MB (alpine/slim).

Kỹ thuật này được Docker hỗ trợ từ phiên bản 17.05 (2017) và hiện là best practice bắt buộc cho mọi Dockerfile production. Xem tài liệu chính thức: Docker Docs – Multi-stage builds.

Sơ đồ minh họa multi-stage build: Stage 1 (builder) với SDK, source code, compile → Stage 2 (runtime) chỉ copy binary từ stage 1

Vấn đề: Dockerfile single-stage truyền thống

Dockerfile truyền thống dùng một base image lớn (golang:1.21, node:20, python:3.11) cho cả build và runtime:

# Dockerfile single-stage - KHÔNG KHUYẾN NGHỊ
FROM golang:1.21
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o myapp .
CMD ["./myapp"]

Kết quả: Image ~1.2GB (chứa Go SDK, compiler, source, cache, module cache). Triển khai chậm, tốn bandwidth, surface attack lớn, vi phạm principle of least privilege.

Giải pháp: Multi-stage build

# syntax=docker/dockerfile:1
# Stage 1: Builder - có đầy đủ SDK, tools
FROM golang:1.21 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /bin/myapp .

# Stage 2: Runtime - chỉ có binary, base siêu nhẹ
FROM gcr.io/distroless/static-debian12 AS runtime
COPY --from=builder /bin/myapp /bin/myapp
USER nonroot:nonroot
ENTRYPOINT ["/bin/myapp"]

Kết quả: Image ~12MB (distroless static) hoặc ~20MB (alpine). Chỉ chứa binary static-linked, không shell, không package manager, không libc (nếu static).

Biểu đồ so sánh size image: Single-stage (1.2GB) vs Multi-stage alpine (45MB) vs Multi-stage distroless (12MB) vs Multi-stage scratch (8MB)

Các base image runtime phổ biến

Base Image Size Đặc điểm Phù hợp
scratch 0B Rỗng hoàn toàn, chỉ binary static Go/Rust static binary, C/C++ static
gcr.io/distroless/static-debian12 ~2-3MB Non-root, certs, timezone, no shell Production Go/Rust, bảo mật cao
gcr.io/distroless/base-debian12 ~10MB Có libc, ca-certs, no shell App dynamic linking (Java, Python)
alpine:3.20 ~5MB Musl libc, apk, shell có sẵn General purpose, debug dễ dàng
debian:12-slim ~80MB glibc đầy đủ, apt App phức tạp cần glibc full
python:3.12-slim ~120MB Python runtime + pip Python app production
node:20-alpine ~60MB Node.js + npm Node.js app production

Lưu ý: Alpine dùng musl libc, có thể gây bug tương thích với một số binary glibc (memory allocator, DNS). Test kỹ trước khi production.

Tối ưu thêm cho từng ngôn ngữ

Go: Static linking + strip symbols

RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /bin/app .
# -s: strip symbol table, -w: strip DWARF debug info
# CGO_ENABLED=0: ép static linking, không phụ thuộc glibc

Rust: Cargo chef + strip

# Stage 1: Cache dependencies với cargo-chef
FROM lukemathwalker/cargo-chef:latest-rust-1.78 AS planner
WORKDIR /app
COPY . .
RUN cargo chef prepare --recipe.json

FROM lukemathwalker/cargo-chef:latest-rust-1.78 AS builder
WORKDIR /app
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe.json
COPY . .
RUN cargo build --release --bin myapp

# Stage 2: Runtime
FROM debian:12-slim AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/myapp /usr/local/bin/myapp

Node.js: Standalone output + alpine

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

# Stage 2: Runtime - Next.js standalone output
FROM node:20-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
CMD ["node", "server.js"]

Python: UV / pip install –user + slim

# Stage 1: Build wheels
FROM python:3.12-slim AS builder
WORKDIR /app
COPY pyproject.toml uv.lock* ./
RUN pip install --no-cache-dir uv && uv pip install --system --no-cache .

# Stage 2: Runtime
FROM python:3.12-slim AS runtime
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
COPY . .
USER 1000:1000
CMD ["python", "main.py"]

Các kỹ thuật nâng cao

Named stages & –target

Đặt tên stage bằng AS và dùng COPY --from=. Build đến stage cụ thể:

docker build --target builder -t myapp:dev .
docker build --target runtime -t myapp:prod .

Hữu ích cho: debug stage (có tools), test stage (có test runner), production stage (minimal).

External image as stage

COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=my-private-registry.com/base:v1.2 /usr/lib/libcustom.so /usr/lib/

Copy artifact từ image riêng biệt (registry, local) mà không cần rebuild.

BuildKit cache mount

# syntax=docker/dockerfile:1.4
FROM golang:1.21 AS builder
RUN --mount=type=cache,target=/go/pkg/mod 
    --mount=type=cache,target=/root/.cache/go-build 
    go build -o /bin/app .

Cache module download và build cache giữa các lần build, giảm thời gian CI/CD đáng kể.

.dockerignore – Bắt buộc

# .dockerignore
.git
.gitignore
README.md
Dockerfile*
docker-compose*.yml
*.md
tests/
*.test
coverage/
.idea/
.vscode/
__pycache__/
*.pyc
node_modules/
dist/
build/
.tmp/
*.log

Loại trừ file không cần thiết khỏi build context — giảm context size, tăng tốc build, tránh leak secret.

Security hardening

  • Non-root user: USER 1000:1000 hoặc USER nonroot:nonroot (distroless)
  • Read-only rootfs: docker run --read-only hoặc config trong Kubernetes
  • Drop capabilities: --cap-drop=ALL
  • No shell: Distroless/scratch không có sh/bash → giảm attack surface
  • Scan image: docker scout cves myapp:latest hoặc Trivy, Grype

Debug multi-stage image

Vì production image không có shell, debug bằng:

# Build debug stage
docker build --target builder -t myapp:debug .
docker run -it myapp:debug sh

# Hoặc copy binary ra host
docker create --name temp myapp:prod
docker cp temp:/bin/myapp ./myapp
docker rm temp

Tóm tắt checklist production

  1. ✅ Dùng multi-stage (builder → runtime)
  2. ✅ Base runtime tối thiểu (distroless/scratch/alpine)
  3. ✅ Static linking (Go/Rust) hoặc minimal deps
  4. ✅ Strip symbols (-s -w)
  5. ✅ Non-root user
  6. ✅ .dockerignore đầy đủ
  7. ✅ BuildKit cache mount
  8. ✅ Scan CVE trước deploy

Multi-stage build không chỉ giảm size — nó tách biệt build-time concerns khỏi runtime concerns, làm Dockerfile dễ maintain, CI/CD nhanh hơn, và production an toàn hơn. Áp dụng ngay hôm nay cho mọi project.

Tôi là một lập trình viên IOS. Code chính là IOS nhưng thỉnnh thoảng vẫn đá sang Android hoặc web. Mặc dù không quá thông thạo nhưng tôi sẽ chia sẻ những kiến thức mà mình đã tìm hiểu, áp dụng qua.

Bài viết liên quan

Bảo mật iPhone: 10 mẹo bảo vệ dữ liệu cá nhân hiệu quả

Bảo mật iPhone: 10 mẹo bảo vệ dữ liệu cá nhân hiệu quả Bảo mật iPhone là chủ đề quan tâm của nhiều người dùng Apple, đặc biệt khi điện…

Xem thêm

Tự động hóa công việc với IFTTT và Zapier trên điện thoại

Tự động hóa công việc với IFTTT và Zapier trên điện thoại Trong thời đại số hóa, việc tự động hoá các tác vụ lặp lại trên điện thoại không…

Xem thêm

Tối ưu hiệu suất Windows 11: 10 mẹo cài đặt tăng tốc máy tính

Tối ưu hiệu suất Windows 11: 10 mẹo tăng tốc máy tính là một trong những chủ đề được nhiều người dùng tìm kiếm khi cảm thấy máy chậm sau…

Xem thêm
0 0 đánh giá
Article Rating
Theo dõi
Thông báo của
guest
0 Comments
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất