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

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"):

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_modulesvà.gitgửi lên daemon, dù chúng chẳng liên quan đếnCOPY 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 độ:
- Tốc độ: context nhỏ gửi nhanh, build khởi động nhanh — đặc biệt khi daemon ở xa/CI.
- Kích thước image: nếu Dockerfile có
COPY . ., mọi thứ trong context (không bị ignore) chui vào image —node_moduleslỗi thời, file build tạm làm image phình to. - Bảo mật: đây là lý do nghiêm trọng nhất.
COPY . .không có.dockerignorecó 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..dockerignorechặ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ề
.là build context, docker gửi cả thư mục lên daemon: đo thật, thư mục 70MB gửi nguyên70.01MBcontext dù Dockerfile chỉCOPY app.py— kể cả file không dùng tới cũng bị gói và gửi..dockerignoreloại rác khỏi context: đo thật, thêm.dockerignore(node_modules/.git/*.log) làm context còn4.096kB(giảm ~17.000 lần) và build nhanh hơn — chênh lệch càng lớn khi daemon ở xa/CI.- Quan trọng nhất là bảo mật và kích thước image:
COPY . .không có.dockerignorekéo.env/.gitvà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
- Docker docs — Build context: https://docs.docker.com/build/concepts/context/
- Docker docs — .dockerignore file: https://docs.docker.com/reference/dockerfile/#dockerignore-file
- Docker docs — Best practices — Exclude with .dockerignore: https://docs.docker.com/build/building/best-practices/#exclude-with-dockerignore
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.