Docker Hub’s free tier imposes rate limits (100 pulls per 6 hours for unauthenticated users) and stores your container images on US servers. A private Docker registry on your Hong Kong VPS eliminates rate limits entirely, keeps proprietary application images on your infrastructure, and — critically for Asia-Pacific teams — makes docker pull operations dramatically faster for servers and CI/CD runners in the region via CN2 GIA routing.
Registry Options Compared
| Option | Features | Complexity | Best For |
|---|---|---|---|
| Docker Registry v2 (official) | Basic push/pull, no UI | Simple | CI/CD automation, teams comfortable with CLI |
| Harbor | Web UI, RBAC, vulnerability scanning, replication | Medium | Enterprise teams, security-conscious deployments |
| Gitea Container Registry | Integrated with Gitea, basic UI | Simple (if using Gitea) | Teams already using Gitea for Git |
Option A: Docker Registry v2 (Simple and Fast)
Step 1: Deploy Registry
<code">apt update && apt upgrade -y
curl -fsSL https://get.docker.com | sh
systemctl enable --now docker
apt install -y nginx certbot python3-certbot-nginx apache2-utils
mkdir -p /opt/registry/{data,auth}
# Generate htpasswd authentication
htpasswd -Bbn registry_user StrongRegistryPassword! > /opt/registry/auth/htpasswd
docker run -d \
--name registry \
--restart unless-stopped \
-p 127.0.0.1:5000:5000 \
-v /opt/registry/data:/var/lib/registry \
-v /opt/registry/auth:/auth \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-e REGISTRY_STORAGE_DELETE_ENABLED=true \
registry:2
docker ps | grep registryStep 2: Nginx with SSL
<code">cat > /etc/nginx/sites-available/registry << 'EOF'
server {
listen 443 ssl http2;
server_name registry.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/registry.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/registry.yourdomain.com/privkey.pem;
# Large image layers need big client body
client_max_body_size 5G;
client_body_timeout 300s;
# Disable request buffering — stream directly to registry
proxy_request_buffering off;
proxy_buffering off;
location /v2/ {
proxy_pass http://127.0.0.1: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 900s;
proxy_connect_timeout 30s;
proxy_send_timeout 900s;
}
}
EOF
ln -s /etc/nginx/sites-available/registry /etc/nginx/sites-enabled/
certbot --nginx -d registry.yourdomain.com
nginx -t && systemctl reload nginxStep 3: Push and Pull Images
<code"># Log in from any Docker client docker login registry.yourdomain.com # Username: registry_user # Password: StrongRegistryPassword! # Tag your image docker tag myapp:latest registry.yourdomain.com/myorg/myapp:latest docker tag myapp:latest registry.yourdomain.com/myorg/myapp:v1.2.0 # Push docker push registry.yourdomain.com/myorg/myapp:latest # Pull from any server (authenticated) docker pull registry.yourdomain.com/myorg/myapp:latest # List repositories curl -u registry_user:StrongRegistryPassword! \ https://registry.yourdomain.com/v2/_catalog # List tags for an image curl -u registry_user:StrongRegistryPassword! \ https://registry.yourdomain.com/v2/myorg/myapp/tags/list
Option B: Harbor (Enterprise Registry with Web UI)
Install Harbor
<code"># Harbor requires Docker Compose
HARBOR_VERSION="v2.11.0"
wget https://github.com/goharbor/harbor/releases/download/${HARBOR_VERSION}/harbor-online-installer-${HARBOR_VERSION}.tgz
tar xzf harbor-online-installer-${HARBOR_VERSION}.tgz
cd harbor
# Configure
cp harbor.yml.tmpl harbor.yml
nano harbor.yml<code"># Key settings in harbor.yml: hostname: harbor.yourdomain.com https: port: 443 certificate: /etc/letsencrypt/live/harbor.yourdomain.com/fullchain.pem private_key: /etc/letsencrypt/live/harbor.yourdomain.com/privkey.pem harbor_admin_password: StrongHarborAdminPass! database: password: StrongDBPass!
<code"># Issue SSL first, then install Harbor certbot certonly --standalone -d harbor.yourdomain.com ./prepare ./install.sh # Harbor starts on ports 80 and 443 docker compose ps
Harbor Key Features
- Web UI — browse repositories, manage tags, view image metadata
- RBAC — project-level permissions: admin, developer, guest roles per registry project
- Vulnerability scanning — Trivy integration scans images for CVEs before they are deployed
- Replication — mirror images to Docker Hub, Amazon ECR, or other Harbor instances
- Content trust — Notary-based image signing
Option C: Gitea Container Registry
If you already run Gitea (see article #78), it includes a built-in container registry compatible with the Docker registry protocol:
<code"># Enable in Gitea app.ini [packages] ENABLED = true # Push images to Gitea registry: docker login git.yourdomain.com docker tag myapp:latest git.yourdomain.com/youruser/myapp:latest docker push git.yourdomain.com/youruser/myapp:latest # Images appear in Gitea under: Packages → Container
Registry Integration with CI/CD
GitHub Actions → HK Registry → HK VPS Deploy
<code"># .github/workflows/deploy.yml
name: Build, Push, Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to private registry
uses: docker/login-action@v3
with:
registry: registry.yourdomain.com
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
registry.yourdomain.com/myorg/myapp:latest
registry.yourdomain.com/myorg/myapp:${{ github.sha }}
cache-from: type=registry,ref=registry.yourdomain.com/myorg/myapp:latest
cache-to: type=inline
- name: Deploy to VPS
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
docker login registry.yourdomain.com \
-u ${{ secrets.REGISTRY_USER }} \
-p ${{ secrets.REGISTRY_PASSWORD }}
docker pull registry.yourdomain.com/myorg/myapp:latest
docker compose -f /opt/myapp/docker-compose.yml up -d --no-deps app
docker image prune -fDocker Compose Using Private Registry
<code"># docker-compose.yml — reference private registry images
version: '3.8'
services:
app:
image: registry.yourdomain.com/myorg/myapp:latest
restart: unless-stopped
# ...
worker:
image: registry.yourdomain.com/myorg/myapp-worker:latest
restart: unless-stopped
# ...Registry Maintenance
<code"># Delete old image tags to reclaim storage
# Using Docker registry API:
REGISTRY="https://registry.yourdomain.com"
CREDS="registry_user:StrongRegistryPassword!"
IMAGE="myorg/myapp"
# Get manifest digest for a tag
DIGEST=$(curl -su $CREDS -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
-I "${REGISTRY}/v2/${IMAGE}/manifests/v1.0.0" \
| grep -i Docker-Content-Digest | awk '{print $2}' | tr -d '\r')
# Delete the manifest (removes the tag)
curl -su $CREDS -X DELETE \
"${REGISTRY}/v2/${IMAGE}/manifests/${DIGEST}"
# Run garbage collection to reclaim disk space
docker exec registry /bin/registry garbage-collect /etc/docker/registry/config.yml<code"># Automated cleanup: delete images older than 30 days cat > /opt/registry-cleanup.sh << 'EOF' #!/bin/bash # Uses registry-cli: pip install registry-cli # registry -l "registry_user:StrongPassword!" \ # -r https://registry.yourdomain.com \ # --delete --num 5 # Keep last 5 tags per image echo "Registry cleanup: $(date)" EOF
Backup the Registry Data
<code"># Backup registry storage volume
crontab -l | { cat; echo "0 3 * * * tar czf /opt/backups/registry_\$(date +%Y%m%d).tar.gz /opt/registry/data/ && find /opt/backups -name 'registry_*' -mtime +7 -delete"; } | crontab -Performance: Pull Speed from Asia
Pulling a 500 MB Docker image from your Hong Kong registry versus Docker Hub:
| Source | Shanghai server pull time (500 MB) |
|---|---|
| Docker Hub (US servers) | 5–20 minutes (congested, throttled) |
| Your HK Registry (CN2 GIA) | 30–90 seconds |
| GitHub Container Registry (US) | 3–15 minutes |
The CN2 GIA speed difference makes a meaningful impact on deployment pipelines — a 10-minute Docker pull delays every deployment for mainland China-based CI runners or production servers. The HK registry turns this into under 2 minutes.
Conclusion
A private Docker registry on a Hong Kong VPS eliminates Docker Hub rate limits, keeps proprietary images on your infrastructure, and makes docker pull operations 5–10× faster for Asia-Pacific servers via CN2 GIA routing. Docker Registry v2 serves most teams with minimal overhead; Harbor adds enterprise features (RBAC, vulnerability scanning, replication) for security-conscious deployments. Both integrate seamlessly with GitHub Actions CI/CD pipelines.
Host your images: Browse Server.HK Hong Kong VPS plans — add a 40–200 GB NVMe volume for image storage and deploy Docker Registry v2 alongside your existing applications.