Tóm tắt: Đo trước (docker image ls, docker history, dive), tìm layer to nhất, rồi xử lý theo thứ tự hiệu quả: (1) multi-stage để bỏ toolchain build, (2) đổi base sang -slim hoặc distroless, (3) dọn cache trong cùng RUN, (4) .dockerignore. Quy tắc viết Dockerfile chung xem Dockerfile – best practices.
Luồng hoạt động
flowchart LR A["Đo: image ls, history"] --> B["dive: layer nào to, file thừa"] B --> C{"Có toolchain build?"} C -->|Có| D["Multi-stage"] C -->|Không| E["Đổi base slim/distroless"] D --> E E --> F["Dọn cache trong cùng RUN"] F --> G["Đo lại, CI=true dive"]
Lệnh
Đo và soi layer
docker image ls app # cột SIZE là dung lượng giải nén
docker history app:latest # kích thước từng layer, lệnh tạo ra nó
docker history --no-trunc --format '{{.Size}}\t{{.CreatedBy}}' app:latest | sort -h | tail
docker buildx imagetools inspect python:3.12-slim # manifest, các platform có sẵn
# dive: duyệt từng layer, xem file thêm/sửa/xoá và "wasted space"
dive app:latest
CI=true dive app:latest # chế độ không tương tác, trả exit codeTrong dive: Tab chuyển giữa panel layer và cây file, Ctrl+U ẩn file không đổi để chỉ thấy file do layer đó thêm. Chỉ số Image efficiency thấp nghĩa là có file bị ghi đè hoặc xoá ở layer sau (vẫn chiếm chỗ ở layer trước).
rules:
lowestEfficiency: 0.95
highestWastedBytes: 20MB
highestUserWastedPercent: 0.10So sánh base image
| Base | Đặc điểm | Lưu ý |
|---|---|---|
debian:bookworm, python:3.12 |
Đầy đủ tool build, vài trăm MB tới ~1 GB | Chỉ dùng cho stage build |
*-slim (Debian) |
glibc, apt, bỏ phần lớn tool build |
Lựa chọn an toàn nhất cho Python/Node |
alpine |
Rất nhỏ (~vài MB), musl libc, apk |
Wheel Python glibc không dùng được, phải build lại; khác biệt DNS/locale của musl |
gcr.io/distroless/static-debian12 |
Không shell, không package manager, có CA cert, tzdata, user nonroot |
Cho binary Go/Rust build tĩnh |
gcr.io/distroless/base-debian12, cc-debian12 |
Thêm glibc (và libstdc++ cho cc) |
Binary link động |
scratch |
Rỗng hoàn toàn | Tự copy CA cert, /etc/passwd nếu cần |
Distroless có tag :nonroot (chạy UID 65532) và :debug (có busybox shell) để debug tạm.
Multi-stage: Go → distroless
# syntax=docker/dockerfile:1
FROM golang:1.23 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/app
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
ENTRYPOINT ["/app"]Kết quả thường từ ~800 MB (golang) xuống còn cỡ dung lượng binary + ~2 MB.
Dọn trong cùng layer
# Sai: rm ở layer sau không làm image nhỏ đi
RUN apt-get update && apt-get install -y build-essential
RUN rm -rf /var/lib/apt/lists/*
# Đúng
RUN apt-get update \
&& apt-get install -y --no-install-recommends libpq5 \
&& rm -rf /var/lib/apt/lists/*
# pip / npm: không giữ cache trong image
RUN pip install --no-cache-dir -r requirements.txt
RUN npm ci --omit=dev && npm cache clean --forceVới Python cần compile extension: cài build-essential ở stage build, tạo venv, rồi chỉ copy /opt/venv sang stage -slim (ví dụ ở trang Dockerfile – best practices).
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|
Đã rm file mà image không nhỏ đi |
File vẫn nằm ở layer trước | Tạo và xoá trong cùng RUN, hoặc dùng multi-stage |
exec /app: no such file or directory trên distroless/scratch |
Binary link động (CGO) nhưng image không có libc | CGO_ENABLED=0, hoặc dùng distroless/base/cc |
x509: certificate signed by unknown authority trên scratch |
Thiếu CA bundle | Dùng distroless/static, hoặc COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ |
pip install trên alpine build rất lâu, lỗi gcc |
Wheel manylinux (glibc) không dùng được với musl | Dùng python:*-slim |
Không kubectl exec ... sh được vào distroless |
Image không có shell | kubectl debug -it <pod> --image=busybox --target=<container> hoặc tag :debug |
| Build context gửi hàng trăm MB | Thiếu .dockerignore (.git, node_modules) |
Thêm .dockerignore |
| Giờ trong log sai múi giờ trên scratch | Không có tzdata | Dùng distroless (có tzdata) hoặc Go import _ "time/tzdata" |