Run a Private Docker Registry with Authentication and TLS

Build a private image distribution point with real auth and HTTPS so your team can pull and push internal images without normalizing public registry exposure.

Private registryTLSAuthenticated image distribution
What you learn

How to deploy the open-source registry image, keep storage persistent, add authentication, terminate TLS safely, and verify that client login and pushes behave as expected.

Best for

Small self-hosted teams that build custom images, want an internal registry, or need a controlled place to store private service images without relying on a public SaaS registry.

Risk to watch

The biggest mistake is running a registry without TLS or with weak authentication and then teaching every host to trust something unsafe.

Before you begin

  • A host or VPS with Docker already running.
  • A domain name for the registry, such as registry.example.com.
  • Persistent storage for registry data.
  • A reverse proxy or certificate path for HTTPS.

The CNCF Distribution project documents the standard registry image and configuration format. Start there instead of pulling random third-party registry stacks unless you have a specific reason.

Step 1: Deploy the open-source registry service

A minimal Compose service looks like this:

services:
  registry:
    image: registry:3
    restart: unless-stopped
    environment:
      REGISTRY_HTTP_ADDR: 0.0.0.0:5000
    volumes:
      - ./registry-data:/var/lib/registry

The Distribution deployment docs describe the registry as an instance of the registry image running in Docker. Keep it simple first: one service, one persistent data path, no public write access until auth and TLS are ready.

Step 2: Keep registry storage persistent and boring

Images are content-addressed blobs. If you lose the registry storage path, you lose the actual image layers and manifests the registry is serving. Use a named volume or a deliberate bind mount and back it up like any other stateful service.

volumes:
  registry-data:

Do not place the registry on ephemeral container storage and assume you can reconstruct it later from memory. You might be able to rebuild some images, but that is not the same as having your registry intact.

Step 3: Add authentication before you let clients use it

The Distribution configuration reference supports multiple auth providers, including htpasswd. For a small internal registry, htpasswd is a practical baseline.

Create credentials with a standard htpasswd-compatible tool and wire them into the registry or the reverse proxy:

mkdir -p auth
htpasswd -Bbn youruser 'strong-password' > auth/htpasswd

If you terminate auth in Nginx instead, keep the policy explicit and document where push rights live. The official Distribution recipes include reverse-proxy examples with authentication in front of the registry API.

Warning: Do not rely on “internal network only” as a substitute for auth if multiple systems or people can reach the registry.

Step 4: Put TLS in front of the registry

Docker clients expect a trustworthy HTTPS endpoint unless you deliberately configure an insecure registry, which you should avoid in production. Terminate TLS with Nginx or another proxy you already trust.

The proxy should forward requests to the private registry upstream while preserving the headers required for Docker clients. Use a real certificate from Let’s Encrypt or an internal PKI your clients already trust.

If you operate in a truly private environment with internal CA certificates, distribute that CA carefully to every Docker host before cutover.

Step 5: Log in and test push and pull

After TLS and auth are in place, verify the client flow from a Docker host:

docker login registry.example.com
docker tag myapp:latest registry.example.com/myteam/myapp:2026-06-10
docker push registry.example.com/myteam/myapp:2026-06-10
docker pull registry.example.com/myteam/myapp:2026-06-10

Test from a second host as well. A registry is only useful once other machines can authenticate, trust the certificate, and pull the exact tag you just pushed.

Expected outcome: Your registry stays private, authenticated, and encrypted, while image distribution becomes easier than copying tarballs around by hand.

Troubleshooting common registry failures

docker login fails with certificate errors.
Fix TLS trust first. Do not “solve” this by normalizing insecure-registry settings on every host unless you intentionally operate that way in a lab.

Pushes fail with authentication errors.
Check whether auth lives in the registry config or the reverse proxy, and verify the credentials file path and permissions.

The registry works locally but not through the domain.
Inspect proxy headers, upstream reachability, and whether the TLS vhost points to the correct backend.

Disk usage grows unexpectedly.
Remember that image layers and old tags consume real space. Monitor storage and add lifecycle discipline around tagging and cleanup.

What to do next

Continue with Build, Tag, and Push a Custom Docker Image With Multi-Stage Builds.