Bạn gõ docker build . và thấy một dòng lạ trôi qua: "Sending build context to Docker daemon 250MB" — rồi đợi. Vì sao "gửi"? Gửi cái gì, đi đâu? Câu trả lời hé lộ một phần kiến trúc Docker mà nhiều người không để ý: build context. Cái dấu . cuối lệnh không chỉ là "thư mục Dockerfile" — nó là toàn bộ thư mục mà Docker sẽ nén và gửi cho daemon trước khi build một byte nào. Bỏ qua điều này khiến build chậm, image phình to, và tệ nhất — rò rỉ bí mật. Bài này (phần 8 loạt Docker) đo thật để thấy build context và cách .dockerignore khống chế nó.

Dấu "." là build context, không chỉ là thư mục Dockerfile

Docker có kiến trúc client–daemon: docker CLI (client) nói chuyện với Docker daemon (server, có thể ở máy khác). Khi build, client phải gửi mọi thứ daemon cần. Cái . trong docker build . chỉ định build context — thư mục được nén (tar) và gửi lên daemon:

docker build .   # "." = build context = cả thư mục hiện tại

Điểm bất ngờ: Docker gửi cả thư mục, kể cả những file Dockerfile không hề dùng tới. Nếu thư mục có node_modules (hàng chục MB), .git (lịch sử), file log, chúng đều bị gói và gửi — lãng phí thời gian và băng thông, dù COPY trong Dockerfile chỉ lấy một file.

.dockerignore: loại file khỏi context

Giải pháp là .dockerignore — hoạt động như .gitignore nhưng cho build context. File nào khớp mẫu trong đó không được đưa vào context:

# .dockerignore
node_modules      # thư viện — cài lại trong image, không cần gửi
.git              # lịch sử git — không cần trong image
*.log             # log rác
.env              # BÍ MẬT — tuyệt đối không được lọt vào image

Ảnh chụp đoạn mã nền tối minh hoạ build context docker build gửi cả thư mục lên daemon dockerignore cứu bạn, dấu chấm trong docker build chấm là build context docker build chấm dấu chấm bằng build context bằng thư mục hiện tại docker nén cả thư mục đó và gửi lên daemon trước khi build kể cả file Dockerfile không hề dùng tới node_modules .git sao log đều bị gói và gửi chậm phí, dockerignore loại file khỏi context như gitignore dockerignore node_modules thư viện cài lại trong image không cần gửi .git lịch sử git không cần trong image sao log log rác .env bí mật không được lọt vào image, ba lý do dockerignore quan trọng một tốc độ context nhỏ gửi nhanh build khởi động nhanh hai kích thước image COPY chấm chấm không kéo rác vào image ba bảo mật chặn .env .git khoá lọt vào image rồi bị đẩy lên registry công khai rò rỉ bí mật kinh điển, lưu ý BuildKit thông minh hơn nhưng vẫn cần dockerignore BuildKit mặc định mới chỉ gửi file được COPY tới nhanh hơn nhưng COPY chấm chấm vẫn kéo mọi thứ và bảo mật kích thước vẫn cần dockerignore đừng bỏ nó chỉ vì BuildKit nhanh

Hình 1: docker build . gửi cả thư mục làm build context lên daemon (kể cả file không dùng); .dockerignore loại rác khỏi context; ba lý do quan trọng: tốc độ, kích thước image, và bảo mật (chặn .env/.git).

Đo thật: 70MB xuống 4KB

Mình dựng một thư mục giống dự án thật: app.py nhỏ + node_modules 48MB + .git 19MB (tổng ~70MB), với Dockerfile chỉ COPY app.py. Đo bằng builder cổ điển (hiện rõ dòng "Sending build context"):

Ảnh chụp bảng kết quả chạy thật build context cộng dockerignore output thật thư mục app.py nhỏ cộng node_modules 48MB cộng .git 19MB tổng 70MB, một không dockerignore gửi cả 70MB dù Dockerfile chỉ COPY app.py Sending build context to Docker daemon 70.01MB, hai có dockerignore node_modules .git sao log 4KB Sending build context to Docker daemon 4.096kB 70MB xuống 4KB giảm 17000 lần, ba thời gian build đọc cộng gửi context không dockerignore 70MB 0.63s có dockerignore vài KB 0.12s chênh nhỏ ở đây vì máy local qua mạng CI thì chênh rất lớn, kết docker gói cả thư mục làm context dockerignore loại rác nhanh hơn image gọn hơn và chặn .env .git lọt vào image

Hình 2: Chạy thật — không .dockerignore, context gửi 70.01MB (dù Dockerfile chỉ COPY app.py); thêm .dockerignore loại node_modules/.git/*.log, context còn 4.096kB (giảm ~17.000 lần); build 0,63s → 0,12s.

  • Không .dockerignore: Sending build context to Docker daemon 70.01MB. Docker gói cả node_modules và .git gửi lên daemon, dù chúng chẳng liên quan đến COPY app.py.
  • Có .dockerignore: context còn 4.096kB — chỉ những file thực sự cần. Giảm khoảng 17.000 lần. Build cũng nhanh hơn (0,63s → 0,12s trên máy local; chênh lệch này nhỏ ở đây vì daemon ở local, nhưng khi daemon ở xa hay trong CI, việc gửi 70MB qua mạng chậm hơn hẳn 4KB).

Ba lý do .dockerignore quan trọng

Không chỉ tốc độ:

  1. Tốc độ: context nhỏ gửi nhanh, build khởi động nhanh — đặc biệt khi daemon ở xa/CI.
  2. Kích thước image: nếu Dockerfile có COPY . ., mọi thứ trong context (không bị ignore) chui vào image — node_modules lỗi thời, file build tạm làm image phình to.
  3. Bảo mật: đây là lý do nghiêm trọng nhất. COPY . . không có .dockerignore có thể kéo .env (chứa mật khẩu, khoá API), thư mục .git (toàn bộ lịch sử, đôi khi có bí mật đã xoá) vào image — rồi image đó bị đẩy lên registry, có khi công khai. Rò rỉ bí mật kinh điển. .dockerignore chặn chúng.

Đánh đổi cần cân nhắc

BuildKit hiện đại thông minh hơn, nhưng đừng bỏ .dockerignore. Builder mặc định mới (BuildKit) không gửi mù cả context — nó phân tích Dockerfile và chỉ gửi file được COPY/ADD tham chiếu, nên nhanh hơn nhiều kể cả không có .dockerignore. Nhưng ba điểm vẫn đúng: COPY . . vẫn kéo mọi thứ (BuildKit vẫn phải gửi cả thư mục cho lệnh đó), và kích thước image lẫn bảo mật không phụ thuộc builder. .dockerignore vẫn là bắt buộc cho dự án nghiêm túc.

.dockerignore khác .gitignore ở vài điểm. Cú pháp gần giống nhưng không hệt: .dockerignore dùng mẫu kiểu Go filepath.Match, có ! để loại trừ ngược, và tính từ gốc build context. Đừng giả định copy nguyên .gitignore là đủ — kiểm lại các mẫu, đặc biệt với thư mục lồng nhau.

Đặt .dockerignore ở gốc context, không phải cạnh Dockerfile. .dockerignore phải nằm ở thư mục gốc của build context (thường là nơi bạn chạy docker build .). Đặt nhầm chỗ (ví dụ trong thư mục con) là nó không có tác dụng, và bạn tưởng đã loại rác mà thật ra không. Kiểm bằng dòng "Sending build context" hoặc kích thước context thực tế.

Ba ý mang về

  1. . là build context, docker gửi cả thư mục lên daemon: đo thật, thư mục 70MB gửi nguyên 70.01MB context dù Dockerfile chỉ COPY app.py — kể cả file không dùng tới cũng bị gói và gửi.
  2. .dockerignore loại rác khỏi context: đo thật, thêm .dockerignore (node_modules/.git/*.log) làm context còn 4.096kB (giảm ~17.000 lần) và build nhanh hơn — chênh lệch càng lớn khi daemon ở xa/CI.
  3. Quan trọng nhất là bảo mật và kích thước image: COPY . . không có .dockerignore kéo .env/.git vào image rồi rò rỉ; BuildKit nhanh hơn nhưng vẫn cần .dockerignore, và nhớ đặt nó ở gốc build context.

Nguồn

Phần sau ta xem kỹ thuật giảm kích thước image mạnh nhất: multi-stage build — tách giai đoạn biên dịch (nặng, đủ công cụ) khỏi giai đoạn chạy (gọn), đo image trước và sau khi tách.