Build graph, cache và reproducible images
Dockerfile mô tả build graph; instruction order và inputs quyết định cache reuse, security và reproducibility. BuildKit giúp thực thi graph này hiệu quả hơn, nhưng một build nhanh chưa chắc đã là một build an toàn, lặp lại được hoặc dễ vận hành.
BuildKit
Hãy đọc Dockerfile như một dependency graph thay vì chỉ như một script tuyến tính. Mỗi instruction tạo ra một state mới dựa trên parent state và input liên quan. Vì vậy thay đổi ở một node có thể làm mất cache của chính node đó và toàn bộ phần downstream. Thiết kế build tốt cần đồng thời tối ưu bốn mục tiêu: tốc độ, reproducibility, security và khả năng điều tra khi build thất bại.
Cache model
BuildKit tính cache từ instruction, parent state và relevant inputs. COPY . . quá sớm khiến thay đổi source code làm invalidate các dependency layers. Với Java/Maven, nên copy manifest/lockfile và wrapper trước, restore dependency, rồi mới copy source. .dockerignore giúp giảm build context, giảm dữ liệu không cần thiết gửi cho builder và giảm nguy cơ vô tình đưa file nhạy cảm vào context.
| Input thay đổi | Ảnh hưởng cache | Thiết kế nên dùng |
|---|---|---|
pom.xml, wrapper hoặc dependency manifest | Invalidate dependency restore và downstream | Đặt trước source để chỉ rebuild dependency khi manifest thật sự đổi. |
| Source code | Invalidate từ bước COPY src ... trở xuống | Copy source sau dependency restore. |
RUN command text không đổi | Có thể reuse cache dù package repository bên ngoài đã đổi | Chủ động refresh cache khi cần patch; không giả định rebuild tự động kéo package mới. |
| Secret value | Nội dung secret không tự làm cache miss | Nếu thay secret phải kéo theo cache bust có chủ đích cho step cần chạy lại. |
Cache mounts
Cache mount như Maven local repository giảm download lặp lại mà không cần bake cache đó vào final image. Cache là optimization, không phải source of truth: pipeline phải vẫn build đúng khi cache bị miss, bị prune hoặc không thể import.
Multi-stage
Builder chứa compiler/toolchain; runtime chỉ copy artifact/runtime files. Có thể dùng stage target cho test/debug. Final image nhỏ hơn giúp giảm attack surface và transfer time, nhưng size nhỏ không tự động đồng nghĩa an toàn: base image vẫn phải được patch, version/digest phải được pin theo risk model và runtime vẫn cần đủ khả năng quan sát/debug khi production có sự cố.
# syntax=docker/dockerfile:1
FROM eclipse-temurin:21-jdk AS build
WORKDIR /src
COPY mvnw pom.xml ./
COPY .mvn .mvn
RUN --mount=type=cache,target=/root/.m2 ./mvnw dependency:go-offline
COPY src src
RUN --mount=type=cache,target=/root/.m2 ./mvnw package -DskipTests
FROM eclipse-temurin:21-jre AS runtime
WORKDIR /app
COPY --from=build /src/target/app.jar /app/app.jar
ENTRYPOINT ["java","-jar","/app/app.jar"]
Nên đặt tên stage thay vì tham chiếu bằng số để việc reorder Dockerfile không làm vỡ COPY --from. Khi cần điều tra, có thể build tới một stage cụ thể bằng --target, ví dụ giữ một stage debug/test riêng nhưng không đưa toolchain đó vào production runtime.
Secrets and provenance
ARG, ENV hoặc COPY không phù hợp để truyền secret vì dữ liệu có thể xuất hiện trong image metadata, layer hoặc cache. Với BuildKit, dùng secret mount cho token/password và SSH mount cho private Git/SSH access để secret chỉ tồn tại tạm thời trong instruction cần nó.
# Build command
DOCKER_BUILDKIT=1 docker build \
--secret id=maven_settings,src=$HOME/.m2/settings.xml \
-t example/app:dev .
# Dockerfile instruction
RUN --mount=type=secret,id=maven_settings,target=/root/.m2/settings.xml \
./mvnw package -DskipTests
Network dependency và version cần được locked hoặc verify để build repeatable. Với base image, tag là mutable; nếu reproducibility/audit yêu cầu mạnh, pin digest và có quy trình cập nhật digest có kiểm soát thay vì để cùng Dockerfile âm thầm resolve sang bytes khác ở lần build sau.
SBOM, provenance và promote by digest
BuildKit/buildx có thể tạo SBOM và provenance attestations. Production pipeline nên gắn metadata này với artifact, scan/verify trước khi release, rồi promote cùng một image digest qua các environment thay vì rebuild lại source cho từng environment. Cách này giảm khoảng trống giữa artifact đã test và artifact thực sự deploy.
docker buildx build \
--platform linux/amd64,linux/arm64 \
--sbom=true \
--provenance=mode=max \
--push \
-t registry.example.com/team/app:1.4.0 .
Failure modes
Các failure mode quan trọng gồm mutable base tag, package index thay đổi, timestamp/non-deterministic archive, cross-platform emulation, cache poisoning, dependency download không verify, registry/build-cache outage và secret/credential hết hạn giữa pipeline.
| Failure window | Signal nên quan sát | Recovery / rollback |
|---|---|---|
| Dependency/base bị đổi hoặc biến mất trong lúc build | Digest mismatch, 404/5xx, checksum failure, dependency resolution latency | Retry có bounded backoff; dùng mirror/cache; quay lại known-good digest/version. |
| Cache bị miss hoặc cache backend unavailable | Cache hit ratio giảm, build duration tăng, network egress tăng | Build vẫn phải đúng khi không có cache; giới hạn concurrency để tránh thundering herd. |
| Cross-platform emulation quá chậm hoặc sai khác | Per-platform duration, test failures chỉ trên một architecture | Dùng native builders cho critical platform; tách artifact theo platform digest; không promote platform fail. |
| Secret hết hạn / permission thay đổi | 401/403 trong dependency fetch hoặc signing | Rotate credential ngoài image; retry step sau khi credential hợp lệ; không log secret. |
| Artifact build được nhưng provenance/SBOM/signing fail | Missing attestation, verification/signature failure | Fail closed cho release path; giữ artifact ở trạng thái unpromoted cho tới khi metadata pass. |
Capacity và operational signals
Builder cũng là production infrastructure. Theo dõi build queue time, build duration theo stage, cache hit/miss, cache storage usage, disk pressure, network/download throughput, registry latency/error rate, CPU/memory của builder và failure rate theo architecture. Cache backend cần quota/GC policy để không làm đầy disk và biến một optimization thành single point of failure.
Security boundaries
- Không cho untrusted Dockerfile dùng chung privileged builder nếu threat model không cho phép; tách worker/policy theo trust boundary.
- Giới hạn network egress nếu build không cần Internet tự do; dùng repository mirror/allowlist khi phù hợp.
- Không copy credentials, SSH keys, local config hoặc toàn workspace vào context; dùng
.dockerignorevà secret mounts. - Verify dependency checksum/signature khi ecosystem hỗ trợ; scan final image và xử lý CVE theo risk/SLA.
Production checklist
- Dependency manifest được copy trước source để tối ưu cache.
.dockerignoreloại bỏ build output, VCS metadata và file nhạy cảm không cần thiết.- Multi-stage tách build toolchain khỏi runtime.
- Secret dùng secret/SSH mount, không dùng
ARG/ENV/COPY. - Base/dependency có strategy pin và automated update rõ ràng.
- Build không phụ thuộc cache để đúng; cache miss chỉ làm chậm.
- CI quan sát duration, cache, disk/network và failure theo stage/platform.
- SBOM/provenance/signature được verify trước promote.
- Release promote cùng digest; rollback quay về known-good digest thay vì rebuild.
Scope note: examples target modern Docker BuildKit/buildx workflows. Exact defaults and feature support depend on Docker Engine/Desktop/buildx/BuildKit version and builder driver/image store; verify the environment used by CI before relying on a specific attestation or cache backend behavior.