Mở đầu — vì sao bài này quan trọng
Bạn đã học cách đóng gói ứng dụng thành container image ở các bài trước. Nhưng có một câu hỏi rất thực tế mà mọi kỹ sư DevOps đều phải trả lời: "Cái image tôi vừa build ra, nó nằm ở đâu, và làm sao để server production kéo nó về chạy?". Câu trả lời chính là container registry — kho lưu trữ image, thứ đóng vai trò như "cửa hàng trung gian" giữa lúc bạn build và lúc bạn deploy.
Nếu ví container image như một kiện hàng đã đóng gói, thì registry chính là kho bãi có địa chỉ rõ ràng, có phân quyền ai được lấy hàng, có quét kiểm tra hàng lỗi, và có phiên bản để bạn biết đây là lô hàng nào. Trong thực tế, phần lớn sự cố deploy trên cloud không đến từ code, mà đến từ những chuyện tưởng nhỏ quanh registry: kéo nhầm tag latest, thiếu quyền truy cập, image phình to gấp 5 lần cần thiết, hoặc registry chứa lỗ hổng bảo mật mà không ai quét.
Bài này tập trung riêng vào hai mảnh ghép: image registry (nơi cất và phân phối image trên cloud) và build (cách tạo ra image một cách chuẩn mực, nhẹ, an toàn và tự động). Đây là nền tảng bắt buộc trước khi bạn động tới các dịch vụ chạy container như ECS, GKE hay AKS. Nắm chắc bài này, bạn sẽ tránh được 80% những lỗi vặt khiến pipeline CI/CD của cả team đứng hình.
Khái niệm cốt lõi
Container Registry là gì
Registry là một dịch vụ lưu trữ và phân phối container image. Mỗi image được định danh bằng cấu trúc registry-host/repository:tag, ví dụ 123456789.dkr.ecr.ap-southeast-1.amazonaws.com/my-app:v1.2.0. Trong đó:
- registry-host: địa chỉ máy chủ registry (thường gắn với region trên cloud).
- repository: "thư mục" chứa các phiên bản của cùng một ứng dụng.
- tag: nhãn phiên bản, ví dụ
v1.2.0,latest, hoặc một commit SHA.
sha256:abc...). Registry lưu các layer này và chia sẻ chung giữa các image — nếu hai image dùng chung base python:3.12-slim, layer đó chỉ lưu một lần. Đây là lý do push/pull image thường nhanh hơn bạn tưởng: chỉ những layer thay đổi mới được truyền đi.Các dịch vụ registry trên cloud
| Dịch vụ | Nhà cung cấp | Đặc điểm nổi bật |
|---|---|---|
| ECR (Elastic Container Registry) | AWS | Tích hợp sâu IAM, quét lỗ hổng, private/public repo |
| Artifact Registry | GCP | Kế thừa Container Registry cũ, hỗ trợ cả image lẫn package (npm, Maven) |
| ACR (Azure Container Registry) | Azure | Có tính năng ACR Tasks build ngay trên cloud, geo-replication |
| Docker Hub | Docker Inc. | Registry công cộng phổ biến nhất, có giới hạn pull rate |
| Harbor | Mã nguồn mở (CNCF) | Tự host, phù hợp private cloud / on-premise VN |
Private vs Public registry
Public registry (như Docker Hub public, ECR Public) cho phép bất kỳ ai kéo image mà không cần đăng nhập — phù hợp để phát hành phần mềm mã nguồn mở. Private registry yêu cầu xác thực, dùng cho image nội bộ chứa mã nguồn hoặc cấu hình nhạy cảm của công ty. Trong doanh nghiệp, gần như 100% image ứng dụng phải nằm ở private registry.
Immutable tag và digest
Một khái niệm dễ bị bỏ qua nhưng cực kỳ quan trọng: tag có thể thay đổi, digest thì không. Bạn hoàn toàn có thể push một image mới đè lên tag v1.2.0 cũ. Điều này nguy hiểm vì production đang chạy v1.2.0 nghĩ rằng mình chạy image A, nhưng thực tế đã bị đổi thành image B. Vì vậy, môi trường production nghiêm túc thường bật chế độ immutable tags (không cho ghi đè tag) hoặc deploy theo digest thay vì tag, để đảm bảo tính bất biến tuyệt đối.
Build image: từ Dockerfile tới image nhẹ
Build là quá trình biến Dockerfile thành image. Ba nguyên tắc vàng khi build cho cloud:
- Multi-stage build: tách giai đoạn build (cần compiler, thư viện dev) khỏi giai đoạn runtime (chỉ cần binary). Kết quả là image cuối nhẹ hơn rất nhiều.
- Base image tối giản: dùng
alpine,slim, hoặcdistrolessthay vì imageubuntuđầy đủ. Image nhỏ hơn nghĩa là push/pull nhanh hơn, bề mặt tấn công bảo mật ít hơn. - Tận dụng cache layer: sắp xếp Dockerfile để những phần ít thay đổi (cài dependencies) nằm trên, phần hay thay đổi (copy source code) nằm dưới, giúp build lại nhanh gấp nhiều lần.
Build ở đâu
Bạn có thể build image ngay trong pipeline CI (GitHub Actions, GitLab CI, Jenkins) rồi push lên registry. Hoặc dùng dịch vụ build của chính cloud: AWS CodeBuild, Google Cloud Build, ACR Tasks. Ưu điểm của build trên cloud là gần registry (push nhanh), không cần cài Docker daemon trên máy runner, và một số dịch vụ như BuildKit, Kaniko, Buildpacks cho phép build image mà không cần quyền root — an toàn hơn trong môi trường Kubernetes.
Tình huống thực tế
Ví dụ 1 — Startup fintech ở TP.HCM và cơn ác mộng tag latest
Một startup fintech khoảng 25 người tại Quận 1 chạy toàn bộ backend Node.js trên ECS của AWS region ap-southeast-1 (Singapore). Pipeline của họ luôn build image và push với tag latest, rồi ECS kéo latest mỗi lần deploy. Mọi thứ ổn cho tới một chiều thứ Sáu: một dev merge nhầm nhánh, pipeline tự chạy, push đè latest bằng bản lỗi. ECS auto-scale ra một task mới lúc 8 giờ tối, kéo latest — và bản lỗi lan ra production. Vấn đề tệ hơn: vì cả bản cũ lẫn bản mới đều mang tag latest, team không thể rollback đơn giản vì không còn tag nào trỏ về bản tốt.
Diễn giải: gốc rễ là dùng tag di động (latest) cho production. Họ sửa bằng cách gắn tag theo commit SHA và số phiên bản (v1.4.2), bật immutable tags trên ECR, và cấu hình ECS deploy theo digest cụ thể. Từ đó rollback chỉ là trỏ lại digest cũ trong vài phút.
Bài học: không bao giờ deploy production bằng latest. Mỗi build phải có một định danh bất biến để có thể tái tạo và rollback chính xác.
Ví dụ 2 — Công ty thương mại điện tử và chi phí lưu trữ registry âm thầm phình to
Một sàn TMĐT tại Hà Nội chạy khoảng 40 microservice trên GKE, dùng Artifact Registry của GCP. Pipeline của họ build và push image cho mỗi commit lên nhánh chính, mỗi ngày khoảng 60–80 image mới, mỗi image trung bình 800 MB (vì dùng base node:18 đầy đủ chưa tối ưu). Sau một năm, hóa đơn lưu trữ registry bất ngờ tăng lên gần 400 USD/tháng chỉ riêng cho việc chứa hàng chục nghìn image cũ chẳng ai dùng.
Diễn giải: họ giải quyết theo hai hướng. Thứ nhất, tối ưu build bằng multi-stage và đổi base sang node:18-slim, kéo kích thước mỗi image từ 800 MB xuống còn khoảng 180 MB. Thứ hai, thiết lập lifecycle policy trên Artifact Registry: chỉ giữ 10 image mới nhất cho mỗi repository và tự xóa image không được tag sau 7 ngày. Chi phí lưu trữ giảm khoảng 70%, thời gian pull image khi scale cũng nhanh hơn rõ rệt.
Bài học: registry không phải kho lưu trữ vô hạn miễn phí. Image nhẹ và chính sách dọn dẹp tự động là hai đòn bẩy vừa tiết kiệm chi phí vừa tăng tốc deploy.
Ví dụ 3 — Đội DevOps ngân hàng và yêu cầu quét bảo mật trước khi lên production
Một ngân hàng đang số hóa hạ tầng, chuyển dần lên nền tảng Kubernetes tự vận hành trên VNG Cloud, dùng Harbor làm private registry vì yêu cầu dữ liệu phải nằm trong nước. Bộ phận an ninh thông tin đặt ra quy định: mọi image trước khi được phép deploy phải được quét lỗ hổng, và image nào chứa lỗ hổng mức Critical sẽ bị chặn.
Diễn giải: Harbor tích hợp sẵn Trivy để quét CVE. Team cấu hình Harbor bật "prevent vulnerable images from running" ở mức Critical. Trong pipeline, sau bước build họ push lên một repo "staging" trong Harbor, chờ kết quả quét; chỉ khi pass mới promote (sao chép) image sang repo "production". Có lần một image bị chặn vì thư viện OpenSSL cũ dính CVE nghiêm trọng — nhờ hàng rào này mà bản dính lỗ hổng không bao giờ chạm tới hệ thống thật.
Bài học: registry không chỉ để lưu trữ, nó là một chốt kiểm soát bảo mật. Quét lỗ hổng ngay tại registry và chặn theo mức độ nghiêm trọng là thực hành bắt buộc trong môi trường tuân thủ cao như ngân hàng.
Hướng dẫn từng bước
Dưới đây là quy trình xây dựng và đẩy image lên registry cloud một cách chuẩn mực, minh họa với AWS ECR (các cloud khác tương tự về nguyên lý).
Bước 1 — Viết Dockerfile multi-stage. Tách giai đoạn build khỏi runtime:
# Giai đoạn build
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Giai đoạn runtime — nhẹ, chỉ chứa thứ cần chạy
FROM node:18-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
Bước 2 — Tạo repository trên registry. Trên AWS:
aws ecr create-repository \
--repository-name my-app \
--image-tag-mutability IMMUTABLE \
--region ap-southeast-1
Chú ý IMMUTABLE để không ai ghi đè được tag.
Bước 3 — Đăng nhập registry. Xác thực Docker với ECR bằng token tạm thời:
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 4 — Build và gắn tag bất biến. Dùng commit SHA hoặc version thay vì latest:
TAG=$(git rev-parse --short HEAD)
docker build -t my-app:$TAG .
docker tag my-app:$TAG \
123456789.dkr.ecr.ap-southeast-1.amazonaws.com/my-app:$TAG
Bước 5 — Push lên registry.
docker push 123456789.dkr.ecr.ap-southeast-1.amazonaws.com/my-app:$TAG
Bước 6 — Bật quét lỗ hổng. Cấu hình ECR quét tự động khi push (scan on push), hoặc dùng Trivy trong pipeline. Kiểm tra kết quả và chỉ promote image sạch.
Bước 7 — Thiết lập lifecycle policy. Đặt luật tự xóa image cũ để kiểm soát chi phí, ví dụ chỉ giữ 20 image mới nhất và xóa image không tag sau 14 ngày.
Bước 8 — Deploy theo digest. Trong manifest triển khai, tham chiếu image bằng digest (my-app@sha256:...) thay vì tag để đảm bảo chạy đúng bản đã kiểm duyệt.
Lỗi thường gặp & mẹo
Lỗi 1 — Dùng latest cho production. Đây là lỗi phổ biến nhất. latest không đảm bảo bất biến, khiến rollback và truy vết trở nên bất khả thi. Mẹo: luôn gắn tag theo commit SHA hoặc semantic version, và bật immutable tags.
Lỗi 2 — Image quá nặng. Copy cả node_modules dev, cache, file tạm, hoặc dùng base image đầy đủ. Mẹo: dùng multi-stage build, base slim/alpine/distroless, và tạo file .dockerignore để loại bỏ .git, node_modules local, log.
Lỗi 3 — Sắp xếp Dockerfile phá cache. Copy toàn bộ source rồi mới cài dependencies khiến mọi thay đổi code đều build lại từ đầu. Mẹo: copy file khai báo dependencies (package.json, requirements.txt) và cài đặt trước, rồi mới copy source code.
Lỗi 4 — Hardcode credential trong image. Nhét mật khẩu, API key, hoặc .env vào image. Registry lưu vĩnh viễn các layer, nên bí mật bị lộ ngay cả khi bạn xóa ở layer sau. Mẹo: dùng biến môi trường lúc runtime hoặc secrets manager, không bao giờ COPY secret vào image.
Lỗi 5 — Quên phân quyền pull cho môi trường chạy. Cluster Kubernetes hoặc ECS task không có quyền kéo image từ private registry, gây lỗi ImagePullBackOff. Mẹo: gắn đúng IAM role (ECR) hoặc tạo imagePullSecret (Harbor/registry khác) cho service account.
Lỗi 6 — Bỏ qua giới hạn pull rate của Docker Hub. Build pipeline kéo base image công cộng quá nhiều lần bị chặn. Mẹo: mirror base image cần dùng về private registry nội bộ, hoặc dùng registry cache.
Mẹo tổng quát: hãy coi registry là một thành phần hạ tầng nghiêm túc như database — có phân quyền, có backup/replication nếu quan trọng, có quét bảo mật, và có chính sách vòng đời. Đừng để nó thành một "bãi rác image" mọc lên tự phát.
Bài tập thực hành
- Tối ưu image. Lấy một ứng dụng nhỏ bất kỳ (Node, Python, hoặc Go), viết một Dockerfile "ngây thơ" dùng base đầy đủ, đo kích thước image. Sau đó viết lại bằng multi-stage build với base
slim/distroless, đo lại. Ghi nhận tỷ lệ giảm dung lượng và giải thích layer nào biến mất.
- Push lên registry cloud. Tạo một private repository trên ECR, Artifact Registry, hoặc dựng thử Harbor trên một VM. Đăng nhập, gắn tag theo commit SHA, push image và xác minh nó hiện trong console. Thử bật immutable tag rồi cố push đè cùng tag để quan sát registry chặn lại.
- Quét bảo mật. Cài Trivy trên máy hoặc bật scan-on-push, quét image bạn vừa build. Liệt kê các CVE tìm được, thử đổi base image sang phiên bản mới hơn và quét lại để thấy số lỗ hổng giảm.
- Viết lifecycle policy. Soạn một chính sách vòng đời (bằng lời hoặc file JSON) cho registry: giữ bao nhiêu image, xóa image không tag sau bao lâu, và giải thích lựa chọn đó ảnh hưởng chi phí ra sao.
Tóm tắt
Container registry là kho lưu trữ và phân phối image — mắt xích không thể thiếu giữa build và deploy trên cloud. Bạn đã học cấu trúc định danh image (host/repo:tag, digest, layer), các dịch vụ chính (ECR, Artifact Registry, ACR, Docker Hub, Harbor), và sự khác biệt sống còn giữa tag di động và digest bất biến. Về phía build, ba nguyên tắc vàng là multi-stage, base image tối giản, và tận dụng cache layer, kết hợp với các công cụ build trên cloud như CodeBuild, Cloud Build, Kaniko.
Qua ba tình huống thực tế — cơn ác mộng latest của startup fintech, chi phí registry phình to của sàn TMĐT, và hàng rào quét bảo mật của ngân hàng — bạn thấy rằng những sự cố lớn thường bắt nguồn từ thói quen nhỏ quanh registry. Hãy ghi nhớ ba trụ cột: image nhẹ, tag bất biến, và registry được quét bảo mật + dọn dẹp tự động. Nắm chắc những điều này, bạn đã sẵn sàng bước sang các bài về chạy container thực sự trên Kubernetes quản lý ở phần tiếp theo.