
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Teams handling proprietary code or operating under strict data residency requirements often need to self-host a Docker Registry rather than relying on public hubs. Running your own registry eliminates egress fees, keeps intellectual property within your VPC, and satisfies compliance mandates like SOC 2 or ISO 27001 that restrict third-party data processing. This guide covers the production-grade setup of the official Docker Registry v2, including TLS termination, authentication, and lifecycle management.
registry:2 image behind an Nginx reverse proxy handling TLS and Basic Auth. Mount a persistent volume for storage, configure htpasswd for access control, and enable garbage collection to reclaim disk space from deleted layers.Why should you self-host a Docker Registry instead of using Docker Hub?
While Docker Hub is convenient for open-source projects, production environments frequently demand tighter control. When you compare container registry options, self-hosting wins on three specific vectors relevant to engineering teams in Nepal and globally: cost predictability, network latency, and regulatory compliance.
Public registries charge per user or per private repository. For a team of 20 developers building microservices, these costs scale linearly. A self-hosted instance on a $20/month VPS offers unlimited private repositories. In regions like Nepal where international bandwidth can be expensive or throttled, pulling multi-gigabyte base images from US-East servers repeatedly destroys deployment velocity. Hosting locally means pulls happen over LAN or low-latency regional links. Finally, if you handle financial data or government contracts, storing artifacts on foreign infrastructure may violate data sovereignty laws. Self-hosting provides the audit trail and physical isolation required for data residency compliance.
How do you configure a secure Docker Registry with TLS and authentication?
The default registry:2 image ships without authentication or encryption. Exposing it directly to the internet is a critical security failure. You must place it behind a reverse proxy that enforces HTTPS and validates credentials before any request reaches the registry API.
Generate credentials and directory structure
Create a dedicated directory for your registry configuration. Use htpasswd to generate bcrypt-hashed credentials; never store plaintext passwords.
mkdir -p /opt/registry/{auth,certs,data}
docker run --rm --entrypoint htpasswd \
httpd:2 -Bbn myuser mysecurepassword \
> /opt/registry/auth/htpasswd Configure Nginx as a TLS-terminating reverse proxy
Nginx handles the heavy lifting of SSL/TLS and HTTP Basic Auth. The registry itself listens only on localhost or a private Docker network. Below is a minimal, secure server block. Note the client_max_body_size directive; without it, pushing layers larger than 1MB will fail with a 413 error.
server {
listen 443 ssl http2;
server_name registry.example.com;
ssl_certificate /etc/letsencrypt/live/registry.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/registry.example.com/privkey.pem;
client_max_body_size 2G;
chunked_transfer_encoding on;
location /v2/ {
auth_basic "Docker Registry";
auth_basic_user_file /opt/registry/auth/htpasswd;
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $http_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_buffering off;
proxy_request_buffering off;
}
} This configuration ensures that every interaction with the /v2/ API endpoint requires valid credentials. The proxy_buffering off setting is essential for streaming large layer uploads without exhausting proxy memory. For certificate management, follow the standard Let's Encrypt and Certbot setup to automate renewal.
Deploy the registry container
Run the registry with a bind mount for persistence. Do not rely on container filesystems for artifact storage.
docker run -d \
--name registry \
--restart=always \
-v /opt/registry/data:/var/lib/registry \
-e REGISTRY_HTTP_ADDR=127.0.0.1:5000 \
-e REGISTRY_STORAGE_DELETE_ENABLED=true \
registry:2 Setting REGISTRY_STORAGE_DELETE_ENABLED=true is mandatory if you plan to run garbage collection later. Without it, the GC process cannot remove unreferenced blobs even if manifests are deleted.
What is the difference between Docker Registry, Harbor, and cloud-native alternatives?
Choosing the right tool depends on operational complexity versus feature requirements. The official Registry v2 is a storage engine, not a platform. Harbor adds vulnerability scanning, replication, and RBAC but requires PostgreSQL, Redis, and Trivy. Cloud registries like ECR or GCR eliminate ops overhead but tie you to vendor ecosystems and egress pricing.
| Feature | Docker Registry v2 | Harbor | AWS ECR / GCP GAR |
|---|---|---|---|
| Setup Complexity | Low (single container) | High (multi-service) | None (managed) |
| Vulnerability Scanning | No | Built-in (Trivy) | Add-on / Paid |
| RBAC / Projects | No (flat namespace) | Yes | IAM-based |
| Storage Cost | Your disk ($) | Your disk ($) | Vendor rate ($$) |
| Replication | Manual / External | Built-in | Cross-region (paid) |
| Best For | Small teams, air-gapped | Enterprise, compliance | Cloud-native shops |
If you simply need a private place to push images for a Kubernetes cluster or a small dev team, Registry v2 is sufficient. If you require automated CVE scanning before deployment or multi-datacenter replication, evaluate Harbor. For teams already deep in AWS or GCP, the managed option reduces toil despite higher long-term costs. Always align this choice with your broader DevSecOps strategy; a registry without scanning is just storage, not security.
How do you manage storage and run garbage collection safely?
Docker Registry uses content-addressable storage. When you delete a tag or manifest, the underlying blob layers are not removed immediately. They remain on disk until garbage collection runs. Neglecting this leads to silent disk exhaustion, a common cause of outages in self-hosted setups.
Garbage collection in Registry v2 is a two-phase mark-and-sweep process. It must run while the registry is in read-only mode to prevent race conditions where a new push references a blob that GC is about to delete. Schedule this during maintenance windows or low-traffic periods.
# Stop writes or switch to read-only config
docker exec registry bin/registry garbage-collect \
--delete-untagged \
/etc/docker/registry/config.yml The --delete-untagged flag is critical. Without it, GC only removes blobs not referenced by any manifest, keeping old tagged versions indefinitely. With the flag, it also cleans up manifests that have no current tags. Always back up your /var/lib/registry directory before running GC for the first time. Monitor disk usage with standard Linux tools; integrating this into your server monitoring routine prevents surprise outages.
How do you integrate a self-hosted registry with CI/CD pipelines?
Authentication in CI differs from interactive use. Pipelines should never use personal credentials. Create a dedicated service account with minimal permissions. In GitHub Actions or GitLab CI, store the username and password as encrypted secrets.
- Login step: Use the official Docker login action or CLI command at the start of the job. Never hardcode credentials in workflow files.
- Tagging strategy: Use semantic versioning plus SHA. Tag images as
v1.2.3andsha-abc1234. Mutable tags likelatestbreak reproducibility and make rollbacks impossible. - Pull-through cache: Configure your registry as a pull-through cache for Docker Hub. This reduces external bandwidth and protects against upstream outages. Add a
proxysection to your registry config pointing tohttps://registry-1.docker.io. - Cleanup: Implement a retention policy. Keep the last N tags per repository. Automate deletion via the Registry HTTP API or tools like
regorcrane.
For Kubernetes deployments, create an imagePullSecret referencing your registry credentials. Ensure your cluster nodes can resolve the registry hostname and trust its TLS certificate. If using self-signed certs internally, distribute the CA to all nodes via update-ca-certificates or equivalent. This integration point is where most self-hosted setups fail; verify connectivity from a worker node before debugging pipeline YAML.
Secure Your Self-Hosted Docker Registry for Production
Running a private registry is straightforward; keeping it secure and reliable requires discipline. Always terminate TLS at a reverse proxy, enforce authentication on every request, and schedule regular garbage collection during maintenance windows. Monitor disk usage proactively and test restore procedures from backup. If your team grows or compliance requirements tighten, plan migration paths to Harbor or managed services before technical debt accumulates. For teams needing hands-on implementation support or security review of their container infrastructure, reach out to discuss your registry architecture.