TL;DR

Docker build không chỉ là "chạy Dockerfile rồi xong". Đằng sau là kiến trúc client-server gồm Buildx và BuildKit, nơi build context quyết định tốc độ và bảo mật toàn bộ quá trình. Thêm .dockerignore đúng cách có thể giảm context từ 245.7MB (15 giây) xuống 2.1MB (0.3 giây) - nhanh hơn 50 lần. Bài này tổng hợp từ tutorial của Destiny Erhabor trên freeCodeCamp, giải thích kiến trúc thực sự và 5+ kỹ thuật tối ưu áp dụng được ngay.

Docker Build Tutorial: Contexts, Architecture, and Performance by Destiny Erhabor

Kiến trúc Docker build: Buildx + BuildKit

Khi bạn gõ docker build ., có hai thành phần đang làm việc cùng nhau:

  • Buildx (client) - giao diện bạn tương tác trực tiếp. Nó nhận lệnh, xử lý tham số, quản lý nhiều BuildKit instances, xử lý authentication và secrets, hiển thị tiến trình.
  • BuildKit (server/engine) - engine thực sự làm mọi việc nặng: đọc Dockerfile từng dòng, thực thi instruction, quản lý layer cache, tạo image cuối.

Khác biệt then chốt so với old Docker builder: BuildKit dùng on-demand file transfer. Thay vì gửi toàn bộ context trước khi build, BuildKit chỉ request file khi cần. Khi gặp COPY package.json ., BuildKit nói với Buildx: "cho tôi package.json" - không hơn không kém. Old builder lại copy tất cả dù bạn chỉ cần 3 file, Docker vẫn chuyển hàng trăm MB trước khi bắt đầu bất kỳ bước nào.

Kiến trúc này còn cho phép scale: nhiều Docker client kết nối cùng một BuildKit instance, BuildKit chạy trên remote server trong khi bạn control từ laptop. Và vì BuildKit chỉ thấy file Dockerfile tham chiếu, secrets không bị embed vào image layers.

Build context - nền tảng của mọi thứ

Build context là tập hợp file và thư mục mà Docker có thể truy cập trong quá trình build. Dockerfile chỉ COPY/ADD được file nằm trong context - vì vậy COPY ../shared/utils.js sẽ fail nếu thư mục cha không nằm trong context đã chỉ định.

Có 4 loại context:

  • Local directory - dùng trong 90% trường hợp: docker build . hoặc docker build ./subdir
  • Remote Git repo - build trực tiếp từ GitHub/GitLab không cần clone local. Phù hợp cho CI/CD và open-source.
  • Remote tarball - từ file .tar.gz trên web server hoặc CDN. Tốt khi build artifacts lưu tập trung.
  • Empty context - khi Dockerfile không cần file nào từ local, dùng stdin để truyền Dockerfile trực tiếp.

Docker client đóng gói tất cả file trong context thành tar archive rồi gửi lên Docker server. Context càng to, build càng chậm - đây là lý do .dockerignore quan trọng đến vậy.

3 sai lầm phổ biến - và cách fix

1. Context quá to (bloated context)

Gốc rễ của hầu hết các build chậm. Để node_modules, .git, build artifacts, logs trong context là sai lầm kinh điển - những thư mục này có thể nặng hàng trăm MB nhưng không cần thiết trong image.

Fix: Tạo file .dockerignore. Kết quả thực đo: context giảm từ 245.7MB (15.2 giây) xuống 2.1MB (0.3 giây) - giảm 99%, nhanh hơn 50 lần. Ngoài performance, .dockerignore còn ngăn leak secrets (.env, private keys, .aws) vào image - một vấn đề bảo mật nghiêm trọng thực tế đã xảy ra với nhiều image public trên DockerHub.

2. Layer caching kém hiệu quả

Lỗi phổ biến: copy toàn bộ source code trước rồi mới chạy npm install. Khi sửa một dòng code, Docker invalidate cache layer đó và buộc chạy lại npm install dù dependencies không đổi. Kết quả: build 5 giây biến thành 5 phút.

Fix: Copy dependency files trước, install, sau đó mới copy source code:

COPY package.json package-lock.json ./
RUN npm install
COPY src/ ./src/

Với thứ tự này, npm install chỉ chạy lại khi package.json thực sự thay đổi, không phải mỗi lần sửa code.

3. Chạy build từ sai thư mục

Chạy docker build frontend/ từ /projects/ khiến context là /projects/frontend/. Nếu Dockerfile cần ../shared/utils.js, build fail ngay - file đó nằm ngoài context. Fix: chạy từ thư mục cha (docker build . -f frontend/Dockerfile) để cả frontend/shared/ đều nằm trong context.

Tối ưu nâng cao

  • Multi-stage builds: Dùng image lớn để build/compile, copy chỉ artifacts cần thiết vào image runtime nhỏ hơn. Loại bỏ build tools, source code, devDependencies khỏi production image. Các stages có thể chạy song song, giảm thêm thời gian build.
  • Slim base images: Alpine Linux (~5MB) thay vì Ubuntu (~29MB) hay Debian (~22MB) - nhỏ hơn 6x. Trong microservices, deploy image 5MB lên 100 server nhanh hơn đáng kể so với 700MB.
  • Combine RUN commands: Mỗi RUN tạo một layer mới. Gộp commands và clean up temp files trong cùng một step để giảm số layers và kích thước image.
  • Build secrets: Dùng --mount=type=secret để truyền API keys, SSH keys vào build mà không lưu vào image layers hay build history.
  • Named contexts: Pull files từ nhiều thư mục hoặc repo khác nhau vào cùng một build, giữ logic tách biệt theo nguồn.

Ai nên đọc bài này

Bài phù hợp với dev đang containerize app lần đầu, hoặc đang bị build mất nhiều phút, hoặc hay gặp lỗi COPY failed: no such file or directory. Tutorial gốc có code examples đầy đủ trên GitHub cho từng kỹ thuật - từ Node.js đến Python, từ single-stage đến multi-stage, từ local context đến remote Git.

Các kỹ thuật trong bài tương thích với Docker mới nhất (BuildKit là default engine từ Docker 23.0) và áp dụng được trực tiếp cho cả local dev lẫn Docker Build Cloud.

Nguồn

Nguồn: freeCodeCamp - Docker Build Tutorial by Destiny Erhabor, Docker Docs - Build Context, Docker Docs - Build Cloud Optimization.