Mở đầu — vì sao bài này quan trọng
Ở các bài trước, bạn đã học cách viết Dockerfile chuẩn và dùng Docker Compose để chạy nhiều service trên máy local. Nhưng có một câu hỏi rất thực tế mà nhiều bạn mới vào nghề hay bỏ qua: sau khi docker build xong một image trên máy CI, làm sao image đó "đi" được tới server production để chạy? Bạn không thể copy nguyên cái image bằng USB, cũng không thể build lại trên mỗi server (vừa chậm, vừa dễ ra kết quả khác nhau). Đây chính là chỗ Container Registry bước vào.
Container Registry là "kho hàng" trung tâm nơi bạn lưu trữ và phân phối container image. Nó đóng vai trò cầu nối giữa hai giai đoạn: CI build ra image rồi push lên registry, sau đó CD (hoặc Kubernetes, hoặc bất kỳ server nào) pull image đó về để chạy. Nếu ví toàn bộ pipeline DevOps như một dây chuyền sản xuất, thì registry chính là kho trung chuyển — không có nó, dây chuyền đứt đoạn.
Hiểu registry không chỉ là biết gõ docker push. Nó liên quan tới bảo mật (ai được pull, ai được push), tới chi phí (lưu trữ và băng thông không hề rẻ khi bạn có hàng nghìn image), tới tốc độ deploy (registry đặt gần cluster hay ở tận bên kia địa cầu?), và tới cả tính ổn định của hệ thống (image bị xóa hoặc tag bị đè có thể làm sập production). Bài này sẽ giúp bạn nắm chắc vai trò của registry, phân biệt các lựa chọn phổ biến (Docker Hub, Amazon ECR, Google GCR/Artifact Registry), và biết cách vận hành registry một cách chuyên nghiệp.
Khái niệm cốt lõi
Registry, Repository, Image và Tag — đừng nhầm lẫn
Bốn khái niệm này rất hay bị dùng lộn xộn, nên hãy phân biệt rõ ngay từ đầu:
- Registry: là dịch vụ (server) lưu trữ nhiều repository. Ví dụ
docker.io(Docker Hub),123456789.dkr.ecr.ap-southeast-1.amazonaws.com(ECR ở Singapore). - Repository: là một "ngăn" trong registry, chứa nhiều phiên bản của cùng một ứng dụng. Ví dụ repository
vietnamcos/web-api. - Image: là một artifact cụ thể — một tập hợp các layer đóng gói ứng dụng.
- Tag: là nhãn gắn vào image để đánh dấu phiên bản, ví dụ
:v1.2.3,:latest,:staging.
registry/repository:tag. Ví dụ docker.io/vietnamcos/web-api:v1.2.3. Khi bạn chỉ gõ docker pull nginx, Docker ngầm hiểu là docker.io/library/nginx:latest — registry mặc định là Docker Hub, tag mặc định là latest.Digest — định danh bất biến của image
Ngoài tag, mỗi image còn có một digest dạng sha256:abc123.... Digest là mã băm của nội dung image, nên nó bất biến: cùng một digest luôn trỏ tới đúng một image duy nhất. Trong khi tag có thể bị đè (hôm nay :latest là image A, mai push đè thành image B), digest thì không bao giờ đổi. Đây là lý do trong môi trường production nghiêm túc, người ta thường deploy theo digest (web-api@sha256:abc...) chứ không theo tag, để đảm bảo tính tái lập tuyệt đối.
Push và Pull hoạt động thế nào
Khi bạn docker push, Docker không tải nguyên cái image lên một cục. Nó tải từng layer riêng biệt. Nếu một layer đã tồn tại trên registry (ví dụ layer base ubuntu:22.04 mà nhiều image cùng dùng), registry sẽ báo "đã có rồi, khỏi tải" — cơ chế này gọi là layer deduplication, giúp tiết kiệm băng thông và dung lượng đáng kể. Đây cũng là lý do vì sao việc tối ưu thứ tự layer trong Dockerfile (đã học ở Bài 16) lại ảnh hưởng trực tiếp tới tốc độ push/pull.
Khi docker pull, quy trình ngược lại: registry gửi về manifest (danh sách các layer cần thiết), Docker kiểm tra layer nào chưa có trên máy thì mới tải về. Vì thế lần deploy đầu tiên bao giờ cũng chậm, các lần sau nhanh hơn nhiều nếu chỉ có vài layer thay đổi.
Registry công khai (public) và riêng tư (private)
- Public registry: ai cũng pull được mà không cần đăng nhập. Phù hợp cho image mã nguồn mở, base image. Rủi ro: bạn không bao giờ nên để image chứa mã nguồn nội bộ hay secret ở dạng public.
- Private registry: cần xác thực (authentication) mới pull/push được. Đây là lựa chọn bắt buộc cho image ứng dụng của công ty.
So sánh các registry phổ biến
| Registry | Nhà cung cấp | Điểm mạnh | Lưu ý |
|---|---|---|---|
| Docker Hub | Docker | Phổ biến nhất, dễ dùng, kho image công khai khổng lồ | Có giới hạn rate limit khi pull ẩn danh; private repo giới hạn ở gói miễn phí |
| Amazon ECR | AWS | Tích hợp sâu với IAM, EKS, ECS; đặt được ở region gần (ap-southeast-1 Singapore) | Trả tiền theo dung lượng lưu trữ + băng thông; xác thực qua token có hạn 12 giờ |
| Google Artifact Registry (kế thừa GCR) | Google Cloud | Tích hợp GKE, hỗ trợ nhiều loại artifact (không chỉ Docker), quét lỗ hổng tích hợp | GCR (gcr.io) đã bị khai tử, phải dùng Artifact Registry |
| GitHub Container Registry (ghcr.io) | GitHub | Gắn liền repo GitHub, miễn phí hào phóng cho public | Xác thực qua GitHub token |
| Harbor (self-hosted) | CNCF | Tự host, kiểm soát hoàn toàn, có quét lỗ hổng, replication | Phải tự vận hành, tốn công ops |
Tình huống thực tế
Tình huống 1 — Startup fintech ở TP.HCM dính "Docker Hub rate limit" giữa giờ cao điểm
Một startup fintech tại TP.HCM (khoảng 25 kỹ sư) dùng Docker Hub để lưu image ứng dụng và cả các base image như node, postgres. Cluster Kubernetes của họ chạy trên một nhà cung cấp cloud, và toàn bộ node đều pull image qua NAT gateway — nghĩa là tất cả traffic đi ra ngoài đều chung một địa chỉ IP.
Docker Hub áp giới hạn 100 lần pull mỗi 6 giờ cho tài khoản ẩn danh (tính theo IP). Vào một buổi sáng thứ Hai, khi họ scale up 15 pod cùng lúc để chuẩn bị cho chiến dịch marketing, mỗi node lại pull nhiều image, và tất cả đều đi qua một IP chung. Kết quả: Docker Hub trả về lỗi toomanyrequests: You have reached your pull rate limit. Hàng loạt pod kẹt ở trạng thái ImagePullBackOff, deploy đứng hình đúng lúc cần nhất.
Bài học rút ra: Đừng phụ thuộc vào pull ẩn danh từ Docker Hub trong production. Giải pháp của họ là (1) đăng nhập tài khoản Docker Hub trả phí để nâng rate limit, và (2) quan trọng hơn, dựng một pull-through cache / mirror registry đặt gần cluster, để mọi base image được cache lại và không phải gọi thẳng ra Docker Hub mỗi lần. Sau đó họ chuyển hẳn image nội bộ sang một private registry của cloud provider đang dùng.
Tình huống 2 — Công ty e-commerce chuyển từ GCR sang Artifact Registry đúng hạn chót
Một công ty thương mại điện tử tại Đông Nam Á chạy toàn bộ hạ tầng trên GKE và dùng gcr.io để lưu image suốt nhiều năm. Google thông báo khai tử Container Registry (GCR) và yêu cầu chuyển sang Artifact Registry. Nhóm DevOps ban đầu chần chừ vì "nó vẫn đang chạy tốt". Đến sát hạn chuyển đổi, họ mới phát hiện có hơn 40 pipeline CI/CD, hàng chục file Kubernetes manifest và Helm chart đang hardcode địa chỉ gcr.io/project-id/....
Việc di chuyển không khó về mặt kỹ thuật (Google cung cấp công cụ copy image sang Artifact Registry và có chế độ tương thích), nhưng khối lượng cập nhật cấu hình rải rác khắp nơi khiến họ mất gần hai tuần rà soát và test lại từng pipeline để chắc chắn không sót chỗ nào.
Bài học rút ra: Đừng hardcode địa chỉ registry rải rác trong code. Hãy đưa nó vào một biến môi trường hoặc biến trong CI/CD (ví dụ REGISTRY=asia-southeast1-docker.pkg.dev/project/repo) để khi cần đổi registry, bạn chỉ sửa một chỗ. Và luôn theo dõi vòng đời (lifecycle) của dịch vụ cloud bạn dùng — công cụ bị khai tử là chuyện xảy ra thường xuyên.
Tình huống 3 — Hóa đơn ECR tăng vọt vì image cũ không bao giờ bị dọn
Một công ty SaaS dùng Amazon ECR ở region Singapore (ap-southeast-1). Pipeline CI của họ build và push một image mới sau mỗi commit vào nhánh main, mỗi image gắn tag theo commit SHA. Nghe rất chuẩn về mặt truy vết, nhưng không ai thiết lập chính sách dọn dẹp. Sau 8 tháng, mỗi repository chứa hàng nghìn image, tổng dung lượng lưu trữ lên tới vài trăm GB, và hóa đơn ECR tăng đều mỗi tháng dù chẳng ai để ý.
Khi review chi phí cuối quý, đội tài chính hỏi tại sao khoản "container storage" lại phình to. Nhóm DevOps kiểm tra thì nhận ra 95% image cũ chưa từng được pull lại lần nào — chúng chỉ nằm đó ngốn tiền.
Bài học rút ra: Luôn thiết lập lifecycle policy cho registry ngay từ đầu. Ví dụ: "giữ lại 20 image gần nhất, xóa image untagged sau 7 ngày, xóa image không phải release sau 30 ngày". ECR, Artifact Registry và Harbor đều hỗ trợ policy tự động này. Chi phí lưu trữ image tưởng nhỏ nhưng tích lũy theo thời gian rất đáng kể (chủ đề này liên quan tới FinOps mà bạn sẽ học sâu hơn ở Bài 43).
Hướng dẫn từng bước
Dưới đây là quy trình chuẩn từ build tới push tới pull, minh họa với cả Docker Hub lẫn ECR.
Bước 1 — Build image với tag đầy đủ. Tag ngay từ lúc build để tránh phải tag lại:
docker build -t vietnamcos/web-api:v1.2.3 .
Bước 2 — Đăng nhập vào registry.
Với Docker Hub:
docker login -u <username>
# nhập Personal Access Token (không dùng mật khẩu tài khoản)
Với Amazon ECR (token chỉ có hạn 12 giờ, nên thường chạy trong CI):
aws ecr get-login-password --region ap-southeast-1 \
| docker login --username AWS \
--password-stdin 123456789.dkr.ecr.ap-southeast-1.amazonaws.com
Bước 3 — Tag image theo đúng địa chỉ registry đích. Với ECR/Artifact Registry, tên repository phải bao gồm cả host của registry:
docker tag vietnamcos/web-api:v1.2.3 \
123456789.dkr.ecr.ap-southeast-1.amazonaws.com/web-api:v1.2.3
Bước 4 — Push lên registry.
docker push 123456789.dkr.ecr.ap-southeast-1.amazonaws.com/web-api:v1.2.3
Với ECR, nhớ tạo repository trước (qua console hoặc aws ecr create-repository --repository-name web-api), vì ECR không tự tạo repo khi push.
Bước 5 — Cấu hình quyền pull ở phía cluster. Nếu là private registry, cluster cần credential để pull. Trong Kubernetes, bạn tạo một imagePullSecret (bài về Secret là Bài 21), hoặc với ECR trên EKS thì gán IAM role cho node group để pull không cần secret.
Bước 6 — Pull và chạy.
docker pull 123456789.dkr.ecr.ap-southeast-1.amazonaws.com/web-api:v1.2.3
docker run -d 123456789.dkr.ecr.ap-southeast-1.amazonaws.com/web-api:v1.2.3
Bước 7 — Tự động hóa trong CI/CD. Trong thực tế, các bước 1–4 nằm gọn trong pipeline. Một đoạn CI điển hình sẽ: login → build → push với tag theo commit SHA và cả tag môi trường (ví dụ :staging). Việc kết nối bước push này với bước deploy tự động là nội dung của các bài CI/CD phía sau.
Lỗi thường gặp & mẹo
Lỗi 1 — Lạm dụng tag :latest. Đây là lỗi kinh điển. :latest không có nghĩa là "phiên bản mới nhất" một cách kỳ diệu — nó chỉ là một cái tên như bao tag khác, và nó bị đè liên tục. Deploy theo :latest khiến bạn không biết chính xác server đang chạy phiên bản nào, và rollback trở thành ác mộng. Mẹo: luôn tag theo phiên bản có ý nghĩa (semver v1.2.3 hoặc commit SHA). Ở production, cân nhắc deploy theo digest.
Lỗi 2 — Đẩy secret vào image rồi push lên public registry. Nhiều người vô tình COPY .env hoặc để lộ API key trong layer, rồi push lên registry public. Kể cả khi bạn xóa file ở layer sau, secret vẫn nằm trong layer trước và ai cũng lôi ra được. Mẹo: dùng .dockerignore cẩn thận, không bao giờ đưa secret vào build args/layer, và bật quét image (scanning — Bài 39) để phát hiện secret lẫn lỗ hổng.
Lỗi 3 — ImagePullBackOff do sai quyền hoặc sai tên. Khi Kubernetes không pull được image, nguyên nhân thường là: thiếu imagePullSecret, IAM role không có quyền ecr:GetDownloadUrlForLayer, gõ sai tên registry/tag, hoặc token ECR đã hết hạn. Mẹo: kubectl describe pod sẽ cho bạn thông báo lỗi cụ thể — đọc kỹ dòng Events, đừng đoán mò.
Lỗi 4 — Không cố định (pin) base image. Dùng FROM node:latest khiến build hôm nay và tháng sau có thể ra kết quả khác nhau vì base image đã đổi. Mẹo: pin theo phiên bản cụ thể, thậm chí theo digest cho những hệ thống cần độ tin cậy cao.
Lỗi 5 — Đặt registry xa cluster. Cluster ở Việt Nam/Singapore mà pull image từ registry đặt ở US sẽ vừa chậm vừa tốn tiền egress. Mẹo: chọn registry cùng region với nơi chạy workload; nếu multi-region, cân nhắc replication để mỗi vùng có bản sao gần nó.
Mẹo cuối — dùng multi-arch image nếu cần. Nếu dev dùng máy Mac ARM (M1/M2) mà server chạy amd64, image build ở local có thể không chạy được trên server. Dùng docker buildx để build multi-arch và push một manifest duy nhất phục vụ cả hai kiến trúc.
Bài tập thực hành
- Push image đầu tiên: Viết một Dockerfile đơn giản cho ứng dụng "hello world" (bất kỳ ngôn ngữ nào), build với tag
<username>/hello:v1, rồi push lên Docker Hub bằng Personal Access Token. Sau đó pull về từ một máy/thư mục khác để xác nhận thành công.
- So sánh digest và tag: Push image
:v1, ghi lại digest (docker inspect --format='{{index .RepoDigests 0}}'). Build lại image có thay đổi nhỏ, push đè cùng tag:v1. Quan sát digest thay đổi thế nào và giải thích tại sao deploy theo digest an toàn hơn theo tag.
- Thiết kế lifecycle policy: Giả sử pipeline của bạn push một image mỗi commit vào main, tag theo commit SHA. Hãy viết ra (bằng lời hoặc JSON) một lifecycle policy hợp lý: giữ lại bao nhiêu image, xóa image untagged sau bao lâu, và giải thích lựa chọn của bạn dựa trên tình huống hóa đơn ECR ở trên.
- Chọn registry cho một kiến trúc cho trước: Một công ty chạy production trên EKS ở Singapore, dev team ở Hà Nội. Họ nên dùng registry nào và đặt ở đâu? Liệt kê lý do về chi phí, tốc độ và bảo mật.
Tóm tắt
Container Registry là kho trung chuyển của mọi pipeline DevOps: CI build image rồi push lên, CD/cluster pull về để chạy. Bạn cần phân biệt rõ registry, repository, image, tag và digest — trong đó digest là định danh bất biến, còn tag thì có thể bị đè. Cơ chế layer deduplication giúp push/pull tiết kiệm băng thông và giải thích vì sao thứ tự layer trong Dockerfile lại quan trọng.
Về lựa chọn: Docker Hub phổ biến và tiện cho image công khai nhưng có rate limit đáng lo trong production; ECR và Artifact Registry là lựa chọn tự nhiên khi bạn chạy trên AWS hoặc Google Cloud nhờ tích hợp IAM và băng thông nội bộ rẻ; Harbor phù hợp khi bạn muốn tự host và kiểm soát hoàn toàn. Nguyên tắc vàng: đặt registry cùng nhà, cùng region với workload.
Ba tình huống thực tế cho thấy các bẫy hay gặp nhất: rate limit của Docker Hub, việc di chuyển khi dịch vụ bị khai tử, và chi phí lưu trữ phình to vì thiếu lifecycle policy. Hãy ghi nhớ ba nguyên tắc vận hành: tag có ý nghĩa (tránh lạm dụng :latest), không bao giờ để lộ secret trong image, và luôn thiết lập chính sách dọn dẹp image cũ ngay từ đầu. Ở các bài tiếp theo, bạn sẽ thấy registry kết nối trực tiếp với Kubernetes (pull image để chạy pod) và với pipeline CI/CD (tự động push sau mỗi lần build).