Hôm qua một bạn trong team nhắn tin: “Anh ơi, docker pull nginx ngồi đợi 15 phút rồi mà chưa xong”. Vấn đề kinh điển mà bất kỳ developer nào làm việc tại Việt Nam — hay bất kỳ nơi nào có kết nối quốc tế không ổn định — đều gặp ít nhất một lần.
Docker Hub đặt server ở Mỹ và châu Âu. Mỗi lần docker pull, bạn đang kéo dữ liệu từ nửa vòng trái đất về. Alpine (~5MB) thì ổn. Nhưng pytorch/pytorch (~6GB)? Hay cả bộ microservices 10–15 image kéo song song trong CI? Mạng quốc tế chập chờn lúc đó không còn là bất tiện nữa — nó là blocker thực sự.
Mình từng gặp tình huống này khi deploy microservices cho dự án e-commerce — không chỉ chậm, mà còn có lần bị lỗi memory leak trong container mà mất 2 ngày mới tìm ra nguyên nhân. Hóa ra image bị kéo dở, layer cache bị corrupt, container khởi động thiếu một số file nhưng không báo lỗi rõ ràng. Từ đó mình nghiêm túc tìm hiểu về Registry Mirror và Pull-through Cache để giải quyết tận gốc.
Registry Mirror và Pull-through Cache là gì?
Registry Mirror
Registry mirror là một server trung gian đứng giữa Docker client của bạn và Docker Hub. Khi bạn chạy docker pull nginx:latest:
- Docker client gửi request đến mirror thay vì thẳng Docker Hub
- Nếu mirror đã có image đó (cache hit) → trả về ngay, tốc độ nhanh như mạng nội bộ
- Nếu mirror chưa có (cache miss) → mirror tự kéo từ Docker Hub, cache lại, rồi trả cho bạn
Pull-through Cache
Pull-through cache đơn giản hơn nghe có vẻ: cứ image nào được kéo qua registry của bạn, nó tự cache lại. Lần sau kéo cùng image — dù là máy khác trong cùng mạng nội bộ — đều lấy từ local, không cần ra internet.
Tại sao nên tự dựng thay vì dùng mirror công cộng?
- Tiết kiệm băng thông khi cả team cùng kéo image — 10 máy pull cùng
node:20-alpine, chỉ tốn 1 lần bandwidth ra ngoài - CI/CD pipeline chạy nhanh hơn nhiều lần (team mình giảm từ ~8 phút xuống ~90 giây)
- Thoát khỏi Docker Hub rate limit — 100 pulls/6h với free account là con số dễ chạm khi CI chạy liên tục
- Hoạt động độc lập ngay cả khi Docker Hub có sự cố
Thực hành: 3 cách cấu hình Registry Mirror
Cách 1: Dùng mirror có sẵn (nhanh nhất)
Chỉ cần giải quyết nhanh? Bạn có thể dùng các mirror công cộng. Nhược điểm: thường không ổn định lâu dài và bạn không kiểm soát được uptime. Mở hoặc tạo file /etc/docker/daemon.json:
sudo nano /etc/docker/daemon.json
Thêm nội dung:
{
"registry-mirrors": [
"https://mirror.gcr.io"
]
}
Sau đó restart Docker và kiểm tra:
sudo systemctl daemon-reload
sudo systemctl restart docker
# Xác nhận cấu hình đã được apply
docker info | grep -A 5 "Registry Mirrors"
Cách 2: Tự dựng Pull-through Cache với Docker Registry
Giải pháp mình đang dùng cho team — ổn định, kiểm soát hoàn toàn, và miễn phí nếu bạn đã có server nội bộ hoặc VPS. Chỉ cần một máy chạy Docker là đủ.
Bước 1: Tạo file cấu hình cho registry
mkdir -p ~/docker-mirror && cd ~/docker-mirror
nano config.yml
Nội dung file config.yml:
version: 0.1
log:
fields:
service: registry
storage:
filesystem:
rootdirectory: /var/lib/registry
delete:
enabled: true
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
proxy:
remoteurl: https://registry-1.docker.io
# Bỏ comment nếu cần auth Docker Hub (tài khoản Pro hoặc private image)
# username: your_dockerhub_username
# password: your_dockerhub_password
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
Bước 2: Chạy Registry với Docker Compose
Tạo file docker-compose.yml:
version: "3.8"
services:
registry-mirror:
image: registry:2
container_name: docker-mirror
restart: always
ports:
- "5000:5000"
volumes:
- ./config.yml:/etc/docker/registry/config.yml:ro
- registry-data:/var/lib/registry
volumes:
registry-data:
Khởi động và kiểm tra:
docker compose up -d
# Kiểm tra registry đang chạy
curl http://localhost:5000/v2/
# Kết quả mong đợi: {}
# Xem logs
docker logs docker-mirror -f
Bước 3: Cấu hình Docker daemon trỏ về mirror vừa dựng
Trên máy client (có thể là chính server đó hoặc máy khác trong cùng mạng), sửa /etc/docker/daemon.json:
{
"registry-mirrors": [
"http://YOUR_SERVER_IP:5000"
],
"insecure-registries": [
"YOUR_SERVER_IP:5000"
]
}
Thay YOUR_SERVER_IP bằng IP thực của server chạy registry, rồi restart Docker.
sudo systemctl daemon-reload
sudo systemctl restart docker
Bước 4: Test thử để thấy sự khác biệt
# Kéo thử image lần đầu (cache miss — kéo từ Docker Hub qua mirror)
time docker pull nginx:alpine
# Xóa image local
docker rmi nginx:alpine
# Kéo lại lần 2 (cache hit — lấy từ mirror local)
time docker pull nginx:alpine
Lần thứ 2 sẽ nhanh hơn rõ rệt. Trên mạng nội bộ của mình, thời gian giảm từ ~45 giây xuống còn ~3 giây với image Alpine.
Cách 3: HTTPS với Nginx reverse proxy (Production-ready)
HTTP trong môi trường production có một phiền phức cụ thể: mỗi máy client phải thêm insecure-registries vào daemon.json. Quản lý 20 máy developer thì việc này mau trở thành ác mộng. Đặt Nginx reverse proxy với Let’s Encrypt giải quyết gọn:
# Cài certbot (Ubuntu/Debian)
sudo apt install certbot python3-certbot-nginx -y
# Lấy certificate
sudo certbot --nginx -d registry-mirror.yourdomain.com
Cấu hình Nginx tại /etc/nginx/sites-available/registry-mirror:
server {
listen 443 ssl;
server_name registry-mirror.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/registry-mirror.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/registry-mirror.yourdomain.com/privkey.pem;
client_max_body_size 0; # Không giới hạn size — cần thiết cho image lớn
chunked_transfer_encoding on;
location / {
proxy_pass http://localhost:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 900; # 15 phút — cần cho image nặng
}
}
Sau khi có HTTPS, daemon.json trên mỗi máy client gọn hơn nhiều:
{
"registry-mirrors": [
"https://registry-mirror.yourdomain.com"
]
}
Không cần insecure-registries nữa.
Quản lý dung lượng cache
Cache tích lũy nhanh hơn bạn nghĩ. Registry của team mình sau 3 tuần đã chiếm khoảng 25GB — chủ yếu từ các base image Node, Python, và nhiều tag khác nhau của cùng một image. Một số lệnh cần biết:
# Xem dung lượng cache hiện tại
docker exec docker-mirror du -sh /var/lib/registry
# Garbage collection — xóa layer không còn được tham chiếu
docker exec docker-mirror registry garbage-collect \
/etc/docker/registry/config.yml
# Reset hoàn toàn cache (xóa tất cả)
docker compose down
docker volume rm docker-mirror_registry-data
docker compose up -d
Kết luận
Nhiều team chỉ nghĩ đến registry mirror sau lần đầu ngồi chờ 20 phút để CI pipeline build xong — hoặc sau khi thấy hóa đơn bandwidth cuối tháng. Với một server nhỏ, thậm chí VPS $5/tháng, bạn giải quyết được cả hai vấn đề đó cho cả team.
Số liệu cụ thể từ setup của mình: thời gian spin-up CI/CD giảm từ ~8 phút còn ~90 giây. Sprint 2 tuần deploy 20–30 lần, con số đó thành hàng giờ tiết kiệm mỗi tháng — chưa kể hết bực bội chờ màn hình terminal đứng yên.
Lưu ý quan trọng trước khi bắt tay: mirror này chỉ cache public image từ Docker Hub. Nếu team dùng thêm GitHub Container Registry (ghcr.io), AWS ECR, hay Google Container Registry, cần dựng mirror riêng cho từng registry — thay proxy.remoteurl thành địa chỉ tương ứng và chạy trên port khác.
