Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Docker Registry with Auth

Set up a Docker registry with authentication in this practical tutorial. Learn to configure TLS, create user credentials, and deploy a secure private registry for your container images.

Focus: set up a docker registry with authentication

Sponsored

You've mastered building images with Dockerfiles and running containers. But here's the reality: every time you run docker pull from Docker Hub, you're trusting a public registry with your application's foundation. For production workloads, proprietary code, or regulated environments, you need your own private registry — a secure, authenticated endpoint that only you and your team can access. Setting up a Docker registry with authentication is the gateway to controlling your container supply chain, and this lesson makes it straightforward.

The problem this lesson solves

Pulling public images from Docker Hub is fine for experimentation. But in production, you hit three walls: - Rate limits: Docker Hub restricts anonymous pulls. - Security: You can't store proprietary images on a public registry without risk. - Control: No access management means no audit trail or team collaboration.

Without a private registry with authentication, every team member needs Docker Hub credentials; you can't enforce image scanning; and you're at the mercy of a third-party service's uptime. Rolling your own registry eliminates these constraints while keeping your images within your infrastructure.

Core concept / mental model

Think of a Docker registry as a git repository for container images. Your local machine is the working directory, Docker Hub is GitHub, and your private registry is a self-hosted GitLab instance. Just as you wouldn't push proprietary code to a public repo, you shouldn't push private images to an unauthenticated registry.

Key definitions

  • Registry: Service that stores and distributes container images, storing layers by content hash.
  • Authentication: Mechanism that verifies identity before allowing pushes or pulls.
  • Authorization: Determines what an authenticated user can do (e.g., read vs. write).
  • TLS: Transport Layer Security — encrypts communication between Docker client and registry.
  • htpasswd: Apache utility that generates password hashes for basic HTTP authentication.

The mental model: The registry is a passive storage API. Authentication acts as a gatekeeper that checks credentials from your Docker client's config file (~/.docker/config.json). Without TLS, credentials are sent in plaintext — so TLS is non-negotiable.

Pro tip: Docker's registry authentication follows the same pattern as basic auth. Your client sends a username:password base64-encoded in the HTTP header. The registry decodes and validates against its password file.

How it works step by step

Setting up an authenticated registry involves three layers:

  1. Provision TLS certificates — Either self-signed for dev or from a CA for production.
  2. Create user credentials — Generate an htpasswd file with hashed passwords.
  3. Deploy the registry container — Mount certificates and auth file as volumes.

Authentication flow

When a Docker client attempts docker push myregistry.example.com/myimage:v1: - Client sends request to registry. - Registry responds with 401 Unauthorized and a Www-Authenticate header. - Client looks up credentials in ~/.docker/config.json. - Client resends the request with Authorization: Basic <base64>. - Registry validates credentials against its htpasswd file. - If valid, registry performs the requested operation. - If invalid, registry returns 401 again.

TLS configuration

Registries require TLS in production. Without it, Docker clients refuse to push or pull by default. You can use: - Let's Encrypt (free, automated) for public-facing registries. - Self-signed certs for internal networks. - Mutual TLS (mTLS) for zero-trust environments.

Hands-on walkthrough

Let's build a production-ready private registry with TLS and authentication on your local machine. We'll use self-signed certificates for this demo.

Prerequisites

  • Docker Engine installed (20.10+)
  • OpenSSL or mkcert available
  • Sudo privileges (for modifying /etc/hosts or firewall rules)

Step 1: Generate TLS certificates

# Create a directory for registry data
mkdir -p ~/registry/{certs,auth,data}
cd ~/registry

# Generate self-signed certificate (for "localhost")
openssl req -newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key \
  -x509 -days 365 -out certs/domain.crt \
  -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

Note: For production, use a real domain and Let's Encrypt. The subjectAltName must match the domain you'll use to access the registry.

Step 2: Create authentication credentials

# Install htpasswd if needed (Ubuntu/Debian)
sudo apt update && sudo apt install apache2-utils -y

# Create a user "admin" with password prompt
docker run --rm --entrypoint htpasswd httpd:alpine -Bbn admin strongPassword123 > auth/htpasswd

# Verify the file
cat auth/htpasswd
# Output: admin:$2y$05$...

-B forces bcrypt hashing (recommended). -b passes password on command line (avoid for security). -n prints to stdout.

Step 3: Start the registry

docker run -d \
  --name secure-registry \
  --restart always \
  -p 443:443 \
  -v $PWD/data:/var/lib/registry \
  -v $PWD/certs:/certs \
  -v $PWD/auth:/auth \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
  -e REGISTRY_AUTH=htpasswd \
  -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  registry:2

Step 4: Log in and test

# Login (use the password you set)
docker login localhost -u admin
# Prompt for password

# Tag and push an image
docker pull alpine:latest
docker tag alpine:latest localhost/alpine:private
docker push localhost/alpine:private
# Output: The push refers to repository [localhost/alpine:private]
# 1f2b5f3c7d9a: Pushed
# v1: digest: sha256:... size: ...

# Verify it's stored
docker exec secure-registry ls /var/lib/registry/docker/registry/v2/repositories/
# Output: alpine

Step 5: Test authentication failure

# Log out and try to pull
docker logout localhost
docker pull localhost/alpine:private
# Output: Error response from daemon: ... unauthorized: authentication required

Important: Every docker pull and docker push to your registry requires valid credentials. Unauthenticated requests get 401.

Compare options / when to choose what

Feature htpasswd (Basic Auth) Token Auth (JWT) LDAP/OIDC Integration
Setup complexity Low (one file) Medium (auth service required) High (external identity provider)
User management Manual file editing API-driven Centralized SSO
Scalability Single instance Stateless, scales horizontally Enterprise-scale
Audit logging Docker daemon logs only Can include user info Full identity tracking
Use case Small teams, dev/test Multi-team, CI/CD pipelines Corporate environments with Active Directory/SAML

For this lesson, htpasswd gives you the best learning-to-production ratio. You can upgrade to token auth later by running Docker Registry Auth Server as a sidecar.

Troubleshooting & edge cases

Common errors and fixes

"x509: certificate is not valid for any names, but wanted to match" - Your subjectAltName is missing localhost. Regenerate the certificate with -addext "subjectAltName=DNS:localhost".

"http: server gave HTTP response to HTTPS client" - Docker client refuses plain HTTP to registries. Either enable TLS or add "insecure-registries": ["localhost:5000"] to /etc/docker/daemon.json.

"Error response from daemon: pull access denied for myimage, repository does not exist" - Check your authentication: run docker login again. Verify ~/.docker/config.json has valid credentials.

"failed to authorize: failed to fetch anonymous token" - If using a proxy, the registry can't reach Docker Hub for image verification. Set REGISTRY_PROXY_REMOTEURL environment variable correctly.

"Permission denied" when pushing as non-admin - Default registry image doesn't enforce authorization — any authenticated user can push. Use Portus or Harbor for fine-grained permissions.

Edge case: Self-signed certs with Docker daemon

Docker daemon doesn't trust self-signed certificates. You must either:

# Copy the CA cert to the system trust store
sudo cp ~/registry/certs/domain.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
sudo systemctl restart docker

Or (less secure):

// /etc/docker/daemon.json
{
  "insecure-registries": ["localhost:443"]
}

Always prefer the CA trust method for anything beyond local testing.

What you learned & what's next

You've achieved the core goal: set up a Docker registry with authentication. You can now: - Generate TLS certificates for secure communication. - Create and manage htpasswd-based authentication. - Deploy and configure the official registry:2 image. - Push and pull images with credential requirements.

The authentication flow you learned (401 challenge → credentials → 200 OK) is the same pattern used by Docker Hub, Harbor, Amazon ECR, and Google Container Registry. This knowledge directly transfers to any OCI-compliant registry.

Your next step: In Lesson 64: Registry with TLS and Let's Encrypt, you'll automate certificate renewal and expose your registry to the internet safely. You'll also learn how to integrate it with your CI/CD pipeline so builds automatically push to your private registry.

Practice recap

Extend our setup: add a second user with read-only access via a different htpasswd entry. Then write a docker-compose.yml that deploys the registry alongside an Nginx reverse proxy that terminates TLS. Tag and push a multi-architecture image (e.g., buildx build --platform linux/amd64,linux/arm64 -t localhost/myapp .) to verify registry handles multi-arch manifests correctly.

Common mistakes

  • Forgetting to add subjectAltName to self-signed certificates — Docker validates the cert name matches the registry hostname.
  • Using plain HTTP without TLS — Docker daemon refuses connections unless you configure insecure-registries, which is insecure.
  • Storing passwords in plaintext in htpasswd — always use bcrypt (-B flag) to hash passwords securely.
  • Not restarting Docker after adding self-signed certs to system trust — the daemon caches CA bundles until restart.

Variations

  1. Replace htpasswd with a token-based auth server (e.g., Docker Registry Auth Server or Keycloak) for JWT-based authentication.
  2. Use Harbor as a full-featured registry with RBAC, vulnerability scanning, and replication built-in.
  3. Host the registry behind an Nginx reverse proxy for rate limiting, IP whitelisting, or additional logging.

Real-world use cases

  • A startup stores proprietary ML model images in a private registry to protect intellectual property while sharing across a team of data scientists.
  • A regulated fintech company runs an on-premises registry for payment processing images, enforced with TLS and LDAP authentication per SOC 2 requirements.
  • A CI/CD pipeline pushes every successful build to a private registry, and only pull requests from reviewed branches can release to production via registry access control.

Key takeaways

  • A private Docker registry with authentication gives you control over security, rate limits, and image distribution.
  • Three mandatory components: TLS certificates, an htpasswd user file, and the registry:2 container with proper environment variables.
  • The authentication flow: Docker client sends credentials (via basic auth) which the registry validates against the htpasswd file.
  • Self-signed certs require updating the system CA trust store OR using insecure-registries in daemon.json.
  • htpasswd is ideal for small teams; upgrade to token auth or Harbor for multi-team enterprise setups.
  • Every docker push and docker pull to an authenticated registry triggers a 401 challenge/response cycle.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.