Self-Host a Docker Registry

Khimananda Oli 8 min read Virtualization
Self-Host a Docker Registry

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.

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.

CI/CD RunnerNginx ProxyTLS + Basic AuthPort 443 / 5000Registry v2Local VolumeGarbage Collector
Self-hosted Docker Registry architecture with Nginx TLS termination and local storage backend

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.

FeatureDocker Registry v2HarborAWS ECR / GCP GAR
Setup ComplexityLow (single container)High (multi-service)None (managed)
Vulnerability ScanningNoBuilt-in (Trivy)Add-on / Paid
RBAC / ProjectsNo (flat namespace)YesIAM-based
Storage CostYour disk ($)Your disk ($)Vendor rate ($$)
ReplicationManual / ExternalBuilt-inCross-region (paid)
Best ForSmall teams, air-gappedEnterprise, complianceCloud-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.

1. Mark PhaseScan all manifestsBuild reference set2. Sweep PhaseCompare blobs vs refsIdentify orphans3. Delete BlobsRemove unreferencedReclaim disk spaceSafety Requirements• Registry must be READ-ONLY during GC• Run as same user/group as registry• Backup /var/lib/registry before first run
Docker Registry garbage collection mark-sweep phases and safety prerequisites

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.

  1. Login step: Use the official Docker login action or CLI command at the start of the job. Never hardcode credentials in workflow files.
  2. Tagging strategy: Use semantic versioning plus SHA. Tag images as v1.2.3 and sha-abc1234. Mutable tags like latest break reproducibility and make rollbacks impossible.
  3. 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 proxy section to your registry config pointing to https://registry-1.docker.io.
  4. Cleanup: Implement a retention policy. Keep the last N tags per repository. Automate deletion via the Registry HTTP API or tools like reg or crane.

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.

Image Pull Latency Comparison (500MB Image)Docker Hub (Remote)~45-120 secondsSelf-Hosted (LAN/Regional)~2-8 secondsVariable bandwidthEgress costs applyRate limits possibleConsistent throughputZero egress feesNo external dependenciesResult: 10-20x faster deployments, predictable costs
Performance comparison demonstrating latency reduction when using self-hosted Docker Registry vs public hub

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.

Frequently Asked Questions

Use the official registry:3 image. It is lightweight, maintained by the CNCF, and includes all necessary binaries without extra dependencies or bloat.

Place cert.pem and key.pem in a certs directory, mount it to /certs inside the container, and set REGISTRY_HTTP_TLS_CERTIFICATE and REGISTRY_HTTP_TLS_KEY environment variables pointing to those files. Clients must trust this CA or copy certs to their Docker trust store.

Yes. Configure the storage section in config.yml with s3 driver parameters including region, bucket name, access keys, and encrypt boolean. This offloads disk management and scales better than local volumes for production workloads exceeding 500GB of image data.

Minimum 512MB for small teams. Allocate 2GB for registries serving over fifty concurrent pulls or storing thousands of tags to prevent garbage collection pauses.

Token-based authentication using htpasswd or OIDC integrates well with CI pipelines. Avoid basic auth in production since credentials transmit with every request unless TLS terminates at a reverse proxy layer before reaching the registry container.

Run registry garbage-collect /etc/docker/registry/config.yml inside the container after deleting manifests via API. Schedule this weekly via cron since deletion only marks blobs; physical reclamation requires explicit garbage collection passes to free disk space.

Harbor adds vulnerability scanning, replication, and RBAC but requires PostgreSQL, Redis, and Trivy. Choose the default registry for simple caching; pick Harbor when compliance auditing or multi-cluster sync justifies operational complexity and resource overhead.

The client lacks the registry CA certificate. Copy ca.crt to /etc/docker/certs.d/registry.example.com/ca.crt on every Docker host, then restart dockerd. Self-signed certificates require explicit trust configuration unlike public CAs that ship pre-installed in system trust stores.

Configure token authentication with separate scopes. Grant pull scope to unauthenticated users and push scope only to authenticated service accounts. Implement this via an external auth server or nginx basic-auth protecting only the v2 blob upload endpoints.

Standard is 5000 internally. Expose 443 externally via reverse proxy for TLS termination. Avoid exposing 5000 directly since most corporate firewalls block non-standard ports and clients expect HTTPS on 443 by default.

Use skopeo copy or crane copy in a scheduled job. Native registry distribution lacks built-in replication; third-party tools handle manifest copying, tag preservation, and digest verification reliably across air-gapped or geographically distributed environments.

Registry 3.x supports OCI artifacts natively including Helm charts, WASM modules, and SBOMs. Ensure your client tooling uses ORAS or regclient since older docker CLI versions cannot push non-image media types correctly.

Hit /v2/ endpoint for liveness checks returning 200 OK. Export Prometheus metrics via REGISTRY_HTTP_DEBUG_PROMETHEUS_ENABLED=true and scrape /metrics for request latency, storage errors, and authentication failures to detect degradation before pulls fail.

Filesystem-backed registries store metadata alongside blobs. Restore from backup or rebuild catalog by running registry-catalog-rebuild. S3-backed deployments survive node loss but still require periodic integrity validation since eventual consistency can cause temporary manifest mismatches during writes.

Deploy nginx or traefik as reverse proxy with limit_req_zone directives. The registry itself lacks native rate limiting. Apply per-IP or per-token throttling at the proxy layer to protect backend storage from abuse during CI storms.