Chuyển đến nội dung chính

Bài 6: Multi-stage Builds và Tối ưu Docker Image

Kỹ thuật multi-stage builds để giảm kích thước image, layer caching optimization, .dockerignore, Alpine vs Distroless base images, security scanning với Trivy/Snyk, và các chiến lược tối ưu image cho production.

🔒 DevSecOps — Bài 6 Bài 6: Multi-stage Builds và Tối ưu Docker Image

Docker từ Cơ bản đến Nâng cao

Phần 2: Dockerfile và Image Optimization

xdev.asia

1. Multi-stage Builds

Multi-stage builds cho phép sử dụng nhiều FROM statements trong một Dockerfile. Chỉ artifacts cần thiết được copy vào final image, giúp giảm kích thước image đáng kể.

Ví dụ: Go Application

# Stage 1: Build
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server .

Stage 2: Production (chỉ chứa binary)

FROM scratch COPY --from=builder /app/server /server EXPOSE 8080 ENTRYPOINT ["/server"]

Kết quả: Image chỉ ~10MB thay vì ~300MB

Ví dụ: React Application

# Stage 1: Install dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

Stage 2: Build

FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build

Stage 3: Production (Nginx serve static files)

FROM nginx:1.27-alpine COPY --from=builder /app/build /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]

Kết quả: Image ~25MB thay vì ~1.2GB

Ví dụ: Java Spring Boot

# Stage 1: Build với Maven
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B

Stage 2: Production

FROM eclipse-temurin:21-jre-alpine RUN addgroup -S spring && adduser -S spring -G spring WORKDIR /app COPY --from=build --chown=spring:spring /app/target/*.jar app.jar USER spring EXPOSE 8080 HEALTHCHECK --interval=30s --timeout=10s --retries=3
CMD wget --spider -q http://localhost:8080/actuator/health || exit 1 ENTRYPOINT ["java", "-jar", "app.jar"]

2. Layer Caching Optimization

Docker cache mỗi layer. Nếu một layer thay đổi, tất cả layers phía sau bị invalidate.

Chiến lược tối ưu cache

# ❌ Bad: Mọi thay đổi code invalidate npm install
COPY . .
RUN npm ci

✅ Good: Chỉ reinstall khi package.json thay đổi

COPY package*.json ./ RUN npm ci COPY . .

# ✅ Tách system dependencies và app dependencies
FROM python:3.12-slim

# Layer 1: System deps (ít thay đổi)
RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc libpq-dev && \
    rm -rf /var/lib/apt/lists/*

# Layer 2: Python deps (thay đổi khi requirements.txt đổi)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Layer 3: App code (thay đổi thường xuyên)
COPY . .

3. So sánh Base Images

Alpine Linux

# ~7MB, dùng musl libc thay vì glibc
FROM alpine:3.20
RUN apk add --no-cache nodejs npm

Ưu điểm: Rất nhỏ, ít attack surface

Nhược điểm: Có thể gặp compatibility issues với glibc-dependent packages

Distroless (Google)

# Không có shell, package manager, chỉ chứa runtime
FROM gcr.io/distroless/nodejs20-debian12
COPY --from=builder /app /app
CMD ["server.js"]

Ưu điểm: Cực kỳ secure, nhỏ

Nhược điểm: Không thể exec vào debug

Scratch

# Empty image, 0 bytes
FROM scratch
COPY --from=builder /app/binary /binary
ENTRYPOINT ["/binary"]

Chỉ cho static binaries (Go, Rust)

4. Security Scanning

Trivy

# Scan image cho vulnerabilities
trivy image myapp:latest

Scan với severity filter

trivy image --severity HIGH,CRITICAL myapp:latest

Scan Dockerfile

trivy config Dockerfile

Fail nếu có critical vulnerabilities

trivy image --exit-code 1 --severity CRITICAL myapp:latest

Docker Scout

# Analyze image
docker scout quickview myapp:latest

Chi tiết vulnerabilities

docker scout cves myapp:latest

So sánh 2 images

docker scout compare --to myapp:v1.0 myapp:v2.0

5. BuildKit Features

# Enable BuildKit
export DOCKER_BUILDKIT=1

Hoặc trong daemon.json

{ "features": { "buildkit": true } }

# Cache mount - cache package downloads
RUN --mount=type=cache,target=/root/.npm \
    npm ci

# Secret mount - build với secrets mà không bị lưu trong layer
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci

# SSH mount - dùng SSH keys trong build
RUN --mount=type=ssh \
    git clone [email protected]:private/repo.git
# Build với secrets
docker build --secret id=npmrc,src=.npmrc -t myapp .

# Build với SSH
docker build --ssh default -t myapp .

6. Chiến lược tối ưu theo ngôn ngữ

Node.js

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY . .
USER node
CMD ["node", "server.js"]

Python

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
USER nobody
CMD ["python", "app.py"]

Go

FROM golang:1.22-alpine AS build
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /server .

FROM scratch COPY --from=build /server /server ENTRYPOINT ["/server"]

7. Tổng kết

Trong bài này, bạn đã nắm được:

  • Multi-stage builds cho Go, Node.js, Java

  • Layer caching optimization strategies

  • So sánh Alpine vs Distroless vs Scratch

  • Security scanning với Trivy và Docker Scout

  • BuildKit features: cache mount, secret mount

  • Chiến lược tối ưu theo từng ngôn ngữ

Bài tiếp theo sẽ hướng dẫn làm việc với Docker Registry, Docker Hub và Private Registry.