Ứng dụng chỉ cần một chương trình để chạy, nhưng image lại mang theo compiler, mã nguồn và hàng loạt công cụ build. Docker multi-stage build giúp tách phần tạo ra ứng dụng khỏi phần dùng để vận hành nó. Kết quả cần hướng tới là một image đủ thành phần để chạy đúng, dễ cập nhật và không chứa những thứ chỉ phục vụ quá trình biên dịch.
1. Hai giai đoạn, hai trách nhiệm
Mỗi lệnh FROM mở một stage. Stage build có thể chứa SDK, compiler và công cụ kiểm thử; stage runtime chỉ nhận các tệp cần dùng thông qua COPY --from. Đặt tên stage như build giúp Dockerfile dễ đọc hơn việc tham chiếu bằng số. Đây là cơ chế được mô tả trong tài liệu multi-stage build của Docker.
| Thành phần | Stage build | Stage runtime |
|---|---|---|
| Compiler và SDK | Cần để biên dịch | Thường không cần với ứng dụng đã biên dịch |
| Mã nguồn, dữ liệu test | Cần tùy quy trình | Chỉ giữ nếu ứng dụng thực sự cần |
| Chương trình đã build | Là đầu ra | Là thành phần chính |
| Thư viện, certificate, cấu hình | Theo yêu cầu build | Theo yêu cầu khi chạy |
2. Một ví dụ HTTP server nhỏ bằng Go
Tạo thư mục thử nghiệm chứa main.go, Dockerfile và .dockerignore. Ví dụ dùng Docker với Linux containers; máy host không cần cài Go vì compiler nằm trong stage build. Đây là server minh họa trả một dòng văn bản, chưa phải cấu hình dịch vụ production đầy đủ.
Nội dung main.go:
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello from a multi-stage image")
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
log.Fatal(server.ListenAndServe())
}
Chương trình dùng thư viện chuẩn nên ví dụ này build trực tiếp một file, không cần tải module ngoài. ReadHeaderTimeout giới hạn thời gian đọc request headers, không phải thời hạn cho toàn bộ quá trình xử lý request. Xem tài liệu http.Server.
3. Dockerfile chỉ đưa binary sang bản chạy
# syntax=docker/dockerfile:1
FROM golang:1.26 AS build
WORKDIR /src
COPY main.go .
RUN CGO_ENABLED=0 go build -trimpath -o /out/server ./main.go
FROM scratch AS runtime
COPY --from=build /out/server /server
USER 65532:65532
EXPOSE 8080
ENTRYPOINT ["/server"]
Trong ví dụ chỉ dùng thư viện chuẩn này, CGO_ENABLED=0 tạo chương trình không phụ thuộc thư viện C động. Stage cuối bắt đầu từ scratch, nhận binary và chạy bằng UID/GID không phải root. Không có compiler hoặc mã nguồn được copy sang stage runtime.
Scratch không phải lựa chọn mặc định cho mọi ứng dụng. Nó không cung cấp shell, CA certificates hay dữ liệu múi giờ. Nếu ứng dụng cần gọi HTTPS ra ngoài, dùng timezone có tên hoặc phụ thuộc C, cần bổ sung đúng tài nguyên hoặc chọn runtime base phù hợp. Đừng áp dụng nguyên Dockerfile này cho Node.js, PHP hay Python: các ứng dụng đó thường vẫn cần interpreter và thư viện khi chạy.
Tag golang:1.26 là ví dụ dễ đọc và có thể thay đổi nội dung theo thời gian. Khi cần kiểm soát chính xác đầu vào build, dùng digest đã xác minh và có quy trình cập nhật nó. Tham khảo Docker build best practices.
4. Giữ build context đúng phạm vi
Dù Dockerfile chỉ copy một file, vẫn nên loại các dữ liệu không thuộc quá trình build khỏi context. Ví dụ .dockerignore:
.git
.env
.env.*
*.log
*.key
*.pem
coverage/
tmp/
Điều chỉnh danh sách theo dự án; chẳng hạn chỉ loại certificate nếu build không cần chúng. Docker đọc .dockerignore ở gốc context để loại file trước khi gửi context đến builder. Xem tài liệu build context.
Nếu cần token tải package riêng, dùng BuildKit secret mounts thay vì nhúng token vào Dockerfile, ARG hay ENV. Tách stage không tự bảo đảm bí mật an toàn: script build vẫn có thể ghi chúng vào log hoặc đầu ra được copy sang runtime.
5. Build, chạy và đo thay vì đoán
Trong thư mục thử nghiệm, chạy từng lệnh sau. Máy cần có Docker Engine đang hoạt động; port 8080 và tên container dưới đây phải chưa được dùng:
docker build --pull -t multi-stage-demo:local .
docker run --rm -d --name multi-stage-demo -p 127.0.0.1:8080:8080 multi-stage-demo:local
curl http://127.0.0.1:8080/
docker logs multi-stage-demo
docker stop multi-stage-demo
Kết quả mong đợi của request là Hello from a multi-stage image. Nếu chạy PowerShell, có thể dùng curl.exe để gọi chương trình curl. EXPOSE trong Dockerfile chỉ mô tả port; -p mới thiết lập ánh xạ port. Ví dụ bind vào localhost để kiểm tra tại máy, theo cơ chế trong hướng dẫn chạy container.
Để so sánh image cuối với stage chứa công cụ build:
docker build --target build -t multi-stage-demo:builder .
docker image inspect multi-stage-demo:local --format '{{.Size}}'
docker image inspect multi-stage-demo:builder --format '{{.Size}}'
docker image inspect multi-stage-demo:local --format '{{.Config.User}}'
Image inspect trả metadata để đối chiếu. Trường Size tính bằng byte không đồng nghĩa với lượng dữ liệu nén tải từ registry hoặc phần dung lượng đĩa tăng thêm khi các image chia sẻ layer. Ghi lại kết quả trên cùng kiến trúc và điều kiện build; không mặc định một tỷ lệ giảm dung lượng cố định.
6. Image gọn và build nhanh là hai phép đo riêng
Multi-stage có thể làm image cuối nhỏ hơn nhưng không tự làm bước biên dịch nhanh hơn. Với dự án có dependencies, đặt các bước ít thay đổi trước và bước copy mã nguồn thường xuyên thay đổi sau; cân nhắc cache mount cho bộ công cụ build. Xem hướng dẫn tối ưu build cache.
Khi đánh giá, ghi riêng thời gian build lạnh, build lặp lại, dung lượng image, thời gian tải và thời gian khởi động. Đừng so một lần build đã có cache với lần khác không có cache rồi kết luận Dockerfile mới luôn nhanh hơn.
7. Checklist trước khi dùng cho dự án thật
- Runtime có đủ thư viện, certificate và quyền truy cập cần thiết.
- Chỉ copy đầu ra và tài nguyên thực sự dùng khi chạy.
- Kiểm thử chính image cuối, không chỉ stage build.
- Ứng dụng chạy được với user không phải root và nhận cấu hình đúng cách.
- Có cách xử lý shutdown, kiểm tra sức khỏe và thu thập log phù hợp.
- Theo dõi cập nhật base image, dependencies và lỗ hổng của binary.
Hãy bắt đầu bằng việc liệt kê những gì ứng dụng cần khi chạy, rồi xây dựng stage runtime từ danh sách đó. Một image gọn chỉ có ý nghĩa khi vẫn chạy đúng, quan sát được và có thể cập nhật lặp lại qua pipeline.




Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.