Docker Hub and GitHub Container Registry are convenient, but sometimes you want images on your own infrastructure: private code, no pull rate limits, faster pulls from servers in the same data center, or full control over retention. The official registry image (the CNCF “Distribution” project) provides a lightweight registry you can run in minutes.

1. Run the registry

mkdir -p ~/registry/{data,auth} && cd ~/registry

Create a user with bcrypt-hashed credentials:

docker run --rm --entrypoint htpasswd httpd:2 -Bbn ci 'a-long-random-password' > auth/htpasswd

compose.yaml:

services:
  registry:
    image: registry:2
    restart: unless-stopped
    environment:
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Registry
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
      REGISTRY_STORAGE_DELETE_ENABLED: "true"
    volumes:
      - ./data:/var/lib/registry
      - ./auth:/auth:ro
    ports:
      - "127.0.0.1:5000:5000"
docker compose up -d

2. TLS with Nginx

Docker requires HTTPS for remote registries (except explicitly configured “insecure” ones — don’t). Put Nginx in front:

server {
    listen 80;
    server_name registry.example.com;

    client_max_body_size 0;            # image layers can be large
    chunked_transfer_encoding on;

    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 900;
    }
}
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d registry.example.com

client_max_body_size 0 removes Nginx’s upload limit — without it, pushing layers fails with 413 errors.

3. Push and pull

docker login registry.example.com
docker tag myapp:1.4 registry.example.com/myapp:1.4
docker push registry.example.com/myapp:1.4

On another server:

docker login registry.example.com
docker pull registry.example.com/myapp:1.4

In Compose files: image: registry.example.com/myapp:1.4.

List repositories and tags via the API:

curl -s -u ci:password https://registry.example.com/v2/_catalog
curl -s -u ci:password https://registry.example.com/v2/myapp/tags/list

4. CI/CD integration

In GitHub Actions or GitLab CI, store the registry credentials as secrets and docker login before pushing. Use separate accounts for CI (push) and servers (pull) where possible — the htpasswd backend doesn’t support per-user permissions, so for fine-grained access control look at Harbor (below).

5. Storage and cleanup

Images accumulate. Deleting a tag through the API only removes the reference; disk space is freed by garbage collection:

# delete a manifest by digest (from the Docker-Content-Digest header of a manifest request)
curl -u ci:password -X DELETE https://registry.example.com/v2/myapp/manifests/sha256:…

# then garbage-collect (stop pushes during GC)
docker compose exec registry registry garbage-collect /etc/docker/registry/config.yml --delete-untagged

Plan disk space accordingly, or use an S3-compatible storage backend (REGISTRY_STORAGE=s3 with endpoint settings) — e.g. MinIO.

6. Pull-through cache for Docker Hub

A registry can also act as a mirror for Docker Hub, avoiding rate limits and speeding up repeated pulls on your servers:

    environment:
      REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io

Run it as a separate instance, then configure Docker daemons to use it in /etc/docker/daemon.json: {"registry-mirrors": ["https://mirror.example.com"]}. See Docker Hub rate limits.

7. Backups

Back up ./data (the image layers) and ./auth. Often you don’t need to back up images at all if CI can rebuild them from Git tags — decide which images are irreplaceable.

Alternatives

  • Harbor — full-featured registry with projects, RBAC, vulnerability scanning, replication and a web UI. See Harbor registry.
  • Gitea / GitLab built-in container registries — images live next to your code. See Gitea.
  • Hosted registries (GHCR, Docker Hub) — zero maintenance.

For a feature-rich self-hosted option, see Harbor. A registry next to your servers — for example on a Storage VPS in the same location — makes deployments fast.

  • Docker
  • Registry
  • Self-Hosting