Frontdoor — Self-hosted Docker Registry on R2
Status: Proposal — chưa implement Author: Giang • Date: 2026-05-11 Companion docs: SSH-only Workspaces · Batch Job Queue
1. Vấn đề hiện tại
Mọi image (frontdoor/workspace, frontdoor-agent) build local trên Mac, không push đâu cả. Khi:
- Lenovo P360 về → không có cách nào pull image (phải build lại từ source).
- Batch worker cần pull image của user (
python:3.12,node:20, etc.) → mỗi worker pull độc lập từ Docker Hub → rate limit + chậm. - Migrate workspace giữa host → image phải có sẵn ở host đích.
Cần registry trung tâm để:
- Push image build từ Mac (hoặc CI), pull từ worker khác.
- Mirror các public image (Docker Hub, ghcr.io) → tăng tốc + giảm rate limit.
- Lưu image private (workspace image có thể chứa SSH keys pre-loaded, secrets từ Wrangler).
2. Lựa chọn: ghcr.io vs Docker Hub vs self-host
| ghcr.io | Docker Hub | Self-host registry:2 |
|
|---|---|---|---|
| Setup | Tạo PAT, 0 server | Tạo account, 0 server | 1 docker run |
| Cost | Free unlimited (public), free 500MB private | Free 1 private | $0.015/GB/tháng R2 |
| Pull rate limit | Không (auth) | 100/6h (anon) — 200/6h (free) | Không |
| Egress to Lenovo (qua CF Tunnel) | Internet | Internet | Trong CF network (free) |
| Mirror Docker Hub public images | Không | Không | Có (pull-through cache) |
| Private image | Có (free 500MB) | Có (1 free) | Có (R2 storage) |
| Multi-arch | Có | Có | Có |
| Vulnerability scan, signing, RBAC | Có (basic) | Có (paid) | Cần Harbor |
| Setup complexity | Rất thấp | Rất thấp | Trung bình |
| Maintenance | 0 | 0 | GC định kỳ, backup |
Đề xuất hybrid
| Image | Lưu ở | Lý do |
|---|---|---|
frontdoor-agent (public, mã nguồn mở) |
ghcr.io | Public, free, không secrets |
frontdoor/workspace (private, có thể pre-config) |
registry.6iang.com (self-host) | Kiểm soát, có thể chứa config private |
Public batch images (python, node, ffmpeg…) |
registry.6iang.com (mirror) | Pull-through cache → giảm Docker Hub rate limit |
3. Kiến trúc
Components:
registry:2container trên Mac mini (sau này có thể move sang VPS/dedicated server)- Storage backend: R2 (S3-compatible API) — bucket
frontdoor-registry - Routing:
registry.6iang.com→ CF Tunnel → Traefik → registry:5000 - Auth: htpasswd basic auth (NOT CF Access, vì Docker CLI không hỗ trợ cookie/JWT)
4. Push/Pull flow
Push (từ máy build):
docker login registry.6iang.com -u giang→ htpasswd verifydocker push registry.6iang.com/frontdoor/workspace:v1- Layers upload qua CF Tunnel → registry → s3 driver → R2 bucket
- Manifest commit cuối cùng
Pull (từ worker):
- Docker daemon nhận request pull image
- Resolve
registry.6iang.comqua DNS → CF - CF Tunnel route → registry container
- registry serve manifest + blob từ R2
- Worker cache layers vào
/var/lib/docker/overlay2
Mirror (pull-through cache cho public images):
- Worker
docker pull python:3.12(configured với registry-mirror) - Daemon redirect →
registry.6iang.com/v2/library/python/manifests/3.12 - Registry miss → fetch từ Docker Hub upstream
- Cache vào R2 → serve về worker
- Pull tiếp theo của worker khác → hit cache, không qua Docker Hub
5. Setup chi tiết
5.1. Tạo R2 bucket + credentials
# Tạo bucket (qua wrangler)
wrangler r2 bucket create frontdoor-registry
# Tạo R2 API token với quyền Read+Write trên bucket này
# Dashboard: Cloudflare → R2 → Manage R2 API Tokens → Create
# Output: ACCESS_KEY_ID + SECRET_ACCESS_KEY
5.2. Setup htpasswd
mkdir -p ~/registry-auth
docker run --rm --entrypoint htpasswd httpd:2 \
-Bbn giang YOUR_STRONG_PASSWORD > ~/registry-auth/htpasswd
chmod 600 ~/registry-auth/htpasswd
5.3. Config registry
~/registry-config/config.yml:
version: 0.1
log:
level: info
formatter: text
storage:
s3:
accesskey: ${R2_ACCESS_KEY_ID}
secretkey: ${R2_SECRET_ACCESS_KEY}
region: auto
regionendpoint: https://${CF_ACCOUNT_ID}.r2.cloudflarestorage.com
bucket: frontdoor-registry
encrypt: false
secure: true
v4auth: true
forcepathstyle: true
rootdirectory: /docker
delete:
enabled: true # cho phép GC xoá blob
maintenance:
uploadpurging:
enabled: true
age: 168h # purge upload dở dang > 7 ngày
interval: 24h
auth:
htpasswd:
realm: Frontdoor
path: /auth/htpasswd
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
proxy: # pull-through cache cho Docker Hub
remoteurl: https://registry-1.docker.io
username: '' # anonymous; có thể thêm Docker Hub account để tăng rate
password: ''
Lưu ý: mode
proxybiến registry này thành mirror only — không thể push. Để vừa mirror vừa push, cần 2 registry container (port 5000 cho push private, port 5001 cho mirror) hoặc dùng Harbor (có cả 2 trong 1).
5.4. docker-compose entry
Thêm vào docker-compose.yml của frontdoor:
registry:
image: registry:2.8
restart: unless-stopped
networks: [frontdoor_net]
environment:
REGISTRY_STORAGE_S3_ACCESSKEY: ${R2_ACCESS_KEY_ID}
REGISTRY_STORAGE_S3_SECRETKEY: ${R2_SECRET_ACCESS_KEY}
volumes:
- ~/registry-config/config.yml:/etc/docker/registry/config.yml:ro
- ~/registry-auth:/auth:ro
labels:
- traefik.enable=true
- traefik.http.routers.registry.rule=Host(`registry.6iang.com`)
- traefik.http.routers.registry.tls=true
- traefik.http.services.registry.loadbalancer.server.port=5000
# Tăng max upload size cho image layer lớn
- traefik.http.middlewares.registry-headers.headers.customresponseheaders.Docker-Distribution-API-Version=registry/2.0
# Tách container riêng cho mirror (nếu dùng dual mode)
registry-mirror:
image: registry:2.8
restart: unless-stopped
networks: [frontdoor_net]
environment:
REGISTRY_STORAGE_S3_ACCESSKEY: ${R2_ACCESS_KEY_ID}
REGISTRY_STORAGE_S3_SECRETKEY: ${R2_SECRET_ACCESS_KEY}
REGISTRY_STORAGE_S3_BUCKET: frontdoor-registry-mirror
REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io
labels:
- traefik.enable=true
- traefik.http.routers.mirror.rule=Host(`mirror.6iang.com`)
- traefik.http.routers.mirror.tls=true
- traefik.http.services.mirror.loadbalancer.server.port=5000
5.5. CF DNS + Tunnel
# /etc/cloudflared/config.yml — thêm 2 ingress rules
ingress:
- hostname: registry.6iang.com
service: http://traefik:80
- hostname: mirror.6iang.com
service: http://traefik:80
- hostname: '*.6iang.com'
service: http://traefik:80
5.6. Client cấu hình mirror
Trên Mac (mỗi worker):
// /etc/docker/daemon.json (Linux) hoặc Docker Desktop Settings (Mac)
{
"registry-mirrors": ["https://mirror.6iang.com"]
}
Sau đó docker pull python:3.12 sẽ đi qua mirror tự động.
6. Đăng nhập và sử dụng
# Login (one-time per host)
docker login registry.6iang.com -u giang
# Password: (từ htpasswd)
# Tag + push
docker tag frontdoor/workspace:latest registry.6iang.com/frontdoor/workspace:v1
docker push registry.6iang.com/frontdoor/workspace:v1
# Multi-arch via buildx (push trực tiếp)
docker buildx build \
--platform linux/arm64,linux/amd64 \
-t registry.6iang.com/frontdoor/workspace:v1 \
--push ./image
# Pull từ Lenovo
docker login registry.6iang.com -u giang
docker pull registry.6iang.com/frontdoor/workspace:v1
7. Garbage collection
Registry không tự xoá blob khi delete tag — cần GC định kỳ.
Cron job
# /etc/cron.weekly/registry-gc
#!/bin/bash
docker exec frontdoor-registry \
/bin/registry garbage-collect \
--delete-untagged=true \
/etc/docker/registry/config.yml
Hoặc qua agent job
Thêm vào agent jobs.go:
case "registryGC":
return jobRegistryGC(ctx, docker)
Cron Trigger trên CF Pages enqueue job này mỗi tuần. Agent exec lệnh trong registry container.
8. Cost estimate (R2)
| Mục | Giá | Ước lượng |
|---|---|---|
| Storage | $0.015 / GB / tháng | 50 GB image = $0.75/tháng |
| Class A operations (push, list) | $4.50 / 1M | ~1000/tháng = miễn phí |
| Class B operations (pull, head) | $0.36 / 1M | ~50k/tháng = miễn phí |
| Egress đến CF network | Free | unlimited |
| Egress ngoài CF | Free 10GB/tháng | thường không xảy ra |
Tổng: ~$1/tháng cho registry ~50GB. Rẻ hơn ghcr.io paid tier nhiều.
9. So sánh storage backend
| R2 | Local disk (Mac) | NFS shared | |
|---|---|---|---|
| Setup | API token | 1 volume mount | Cần NFS server |
| Backup | Tự replicated | Cần backup riêng | Tuỳ NFS |
| Multi-host scale | ✓ same R2 cho mọi node | ✗ phải sync | ✓ qua NFS |
| Disaster recovery | Cloudflare global | Mac chết = mất | NFS server chết = mất |
| Cost | $0.015/GB/m | "Free" (đã có ổ) | Server cost |
| Latency push từ Mac | ~50ms (R2 SIN/HKG) | ~1ms | <1ms LAN |
| Latency pull về Mac | ~50ms | ~1ms | <1ms LAN |
| Latency pull về Lenovo (sau khi setup) | ~50ms qua CF | Phải tunnel ra Mac | Phải tunnel ra Mac |
R2 thắng khi có >1 worker. Single-host Mac thì local disk nhanh hơn nhưng mất ưu thế khi thêm Lenovo.
10. Bảo mật
| Threat | Mitigation |
|---|---|
| htpasswd bị brute force | Cloudflare WAF rate limit + chỉ allow IP từ workers (qua CF Access Service Token cho UI ops) |
| Image bị poison (man-in-the-middle) | HTTPS (CF cert), content-addressable storage (image digest verify) |
| Push image độc lên | Chỉ Giang có credentials. Audit qua R2 access log |
| Stolen credentials | Revoke htpasswd entry → regen → push lại |
| R2 bucket public mistake | Bucket private mặc định, set policy explicit |
| Mirror pull credential | Anonymous → có thể bị Docker Hub rate limit. Thêm Docker Hub account vào config để authenticated pull (200/6h → 5000/day) |
11. Comparison: Harbor
Khi nào cân nhắc Harbor thay vì registry:2:
| Feature | registry:2 |
Harbor |
|---|---|---|
| Pull-through proxy cache | ✓ | ✓ |
| Multi-arch | ✓ | ✓ |
| Web UI | ✗ (CLI only) | ✓ (đẹp, search, history) |
| Vulnerability scan (Trivy) | ✗ | ✓ |
| Image signing (Cosign) | ✗ | ✓ |
| RBAC (per-project) | ✗ | ✓ |
| Replication giữa registries | ✗ | ✓ |
| Garbage collection UI | ✗ (CLI) | ✓ |
| Setup | 1 container | docker-compose 6-7 service |
| Tài nguyên | ~50 MB RAM | ~500 MB RAM, postgres + redis |
Đề xuất: Bắt đầu với registry:2. Move sang Harbor khi:
- Cần share workspace với team (multi-user) → RBAC quan trọng
- Compliance yêu cầu vuln scan
- Hoặc >5 workers cần fine-grained access control
12. Implementation plan
| Bước | Hành động | Effort |
|---|---|---|
| 1 | Tạo R2 bucket + API token | 15 phút |
| 2 | Tạo htpasswd file | 5 phút |
| 3 | Viết config.yml + docker-compose entry | 30 phút |
| 4 | DNS + Tunnel ingress | 15 phút |
| 5 | Test push từ Mac | 15 phút |
| 6 | Test pull từ Lenovo (khi máy về) | 15 phút |
| 7 | Setup mirror.6iang.com | 30 phút |
| 8 | Cấu hình registry-mirrors trên workers | 10 phút |
| 9 | GC cron + alert | 30 phút |
| 10 | Migrate workspace image push | 30 phút |
| Tổng | ~3.5 giờ |
13. Push workspace image lên registry
Build workspace image (Dockerfile mới, SSH-only):
# 1. Build local (Mac)
cd ~/workspace/frontdoor/image
docker buildx build \
--platform linux/arm64,linux/amd64 \
-t registry.6iang.com/frontdoor/workspace:v1 \
-t registry.6iang.com/frontdoor/workspace:latest \
--push .
buildx build --push build multi-arch và push trực tiếp, không qua local image store — gọn hơn docker push riêng.
Cập nhật agent dùng image từ registry:
// agent/main.go — đổi env name + default
WorkspaceImage: envOr("WORKSPACE_IMAGE", "registry.6iang.com/frontdoor/workspace:latest"),
Chỉ cần set env trong setup → WORKSPACE_IMAGE=registry.6iang.com/frontdoor/workspace:latest. Agent restart pull tự động.
Lenovo về chạy setup.sh với cùng env → tạo workspace pull cùng image từ registry, không cần build local.
14. Open questions
- Container chạy registry ở đâu? Mac mini hiện đang chạy frontdoor stack. Thêm 1 service vào docker-compose.yml. Tài nguyên: ~50MB RAM, ~5% CPU.
- Backup R2 sang đâu? R2 tự replicated cross-zone. Optional: rclone copy weekly sang Backblaze B2 (chi phí ~$0.005/GB/m). Có cần không cho 50GB image?
- Push từ GitHub Actions vào registry self-host? Cần expose registry với credentials trong GH secrets. Hoặc giữ flow: GitHub Actions build → push ghcr.io → cron job mirror ghcr → self-host registry.
- Image signing?
cosign sign registry.6iang.com/...— không bắt buộc cho single-user nhưng nice. Cần thêm step trong CI. - Mirror Docker Hub có vi phạm ToS? Pull-through cache cho personal use OK. Đừng share mirror.6iang.com public.
- Multiple registries cho phép push lên ghcr.io VÀ self-host cùng lúc? Có — tag image với 2 prefix + push 2 lần. Hoặc dùng skopeo copy giữa 2 registries.
15. Tóm tắt
| Khi nào dùng | Choice |
|---|---|
| 1 worker, demo, học | Build local, không push |
| 1 worker, agent open-source | ghcr.io |
| ≥2 worker, private image | registry:2 + R2 (self-host) |
| Team ≥3 user, audit, vuln scan | Harbor |
Cho roadmap hiện tại (Mac + Lenovo soon): self-host registry:2 backed bằng R2, kèm mirror cho Docker Hub. Setup ~3.5 giờ. Cost ~$1/tháng. Maintenance: GC mỗi tuần (cron).