Deploying Docker Registry with Granular Access Control, TLS & S3
Deploying a self-hosted Docker registry might seem like a simple task, and there are many guides available on how to do it. However, most of these guides cover only the basics. One of the biggest limitations I encountered was the inability to configure different access rights for each user.
This guide demonstrates a production-ready setup using cesanta/docker_auth for advanced access management, ensuring both security and flexible permission configurations.
Key Advantages of This Setup
- Centralized authentication with JWT token validation - All users authenticate via a central service, ensuring consistent access management.
- Fine-grained permissions per user/group/image repository - Each user or team can be assigned specific permissions for different repositories.
- Automatic HTTPS via Caddy reverse proxy - Secure communications with automated TLS certificates from Let's Encrypt.
- Audit logging of all registry operations - Keeps track of all actions performed on the registry.
- Role-based access control (RBAC) for teams - Allows defining roles such as "admin", "developer", etc.
- Secure TLS communication with auto-renewing certificates - Ensures encrypted communication without manual certificate management.
How to Set It Up?
First, we create a docker-compose.yml
file to define all necessary services:
services:
auth:
image: cesanta/docker_auth:1
restart: always
volumes:
- ./conf/auth-server.yml:/config/auth_config.yml
- ./conf/keys:/certs
registry:
image: registry:2
restart: always
environment:
# Where to listen
REGISTRY_HTTP_ADDR: 0.0.0.0:5000
REGISTRY_HTTP_SECRET: somerandomstringhere
# Registry auth
REGISTRY_AUTH: token
REGISTRY_AUTH_TOKEN_REALM: https://${AUTH_DOMAIN}/auth
REGISTRY_AUTH_TOKEN_SERVICE: "Docker registry"
REGISTRY_AUTH_TOKEN_ISSUER: "Acme auth server" # this must match the issuer name in the next config file.
REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE: /certs/certificate.pem
# S3 Storage configuration
REGISTRY_STORAGE: s3
REGISTRY_STORAGE_S3_ACCESSKEY: accesskey
REGISTRY_STORAGE_S3_SECRETKEY: secretkey
REGISTRY_STORAGE_S3_BUCKET: bucketname
REGISTRY_STORAGE_S3_REGION: eu-west-1
volumes:
- ./conf/keys/server.pem:/certs/certificate.pem
caddy:
ports:
- 80:80
- 443:443
image: caddy:2
environment:
AUTH_DOMAIN: ${AUTH_DOMAIN}
REGISTRY_DOMAIN: ${REGISTRY_DOMAIN}
restart: always
volumes:
- ./conf/Caddyfile:/etc/caddy/Caddyfile
This setup includes three core services:
- auth: Handles user authentication and permissions.
- registry: The actual Docker registry.
- caddy: A reverse proxy that provides HTTPS automatically.
Authentication Configuration
The auth
service requires a configuration file (conf/auth-server.yml
):
server:
addr: ":5001"
token:
issuer: "Acme auth server" # Must match issuer in the Registry config.
expiration: 900
key: "/certs/server.key"
certificate: "/certs/server.pem"
users:
# Password is specified as a BCrypt hash. Use `htpasswd -nB USERNAME` to generate.
"username1":
password: "$2y$05$YSouiTqTc36VcV80P8lKXOMGGQRnzoqjQLKg8oar7ZJ0EFil2DtxC" # testing
"username2":
password: "$2y$05$YSouiTqTc36VcV80P8lKXOMGGQRnzoqjQLKg8oar7ZJ0EFil2DtxC" # testing
# example with labels
"admin":
password: "$2y$05$YSouiTqTc36VcV80P8lKXOMGGQRnzoqjQLKg8oar7ZJ0EFil2DtxC" # testing
labels:
role: ["administrator"]
"developer":
password: "$2y$05$YSouiTqTc36VcV80P8lKXOMGGQRnzoqjQLKg8oar7ZJ0EFil2DtxC" # testing
labels:
team: ["backend"]
role: ["developer"]
"": {} # Allow anonymous (no "docker login") access.
# Access is denied by default.
acl:
- match: { account: "username1" }
actions: ["*"]
comment: "username1 has full access to everything."
- match: { account: "username2" }
actions: ["*"]
comment: "username2 has full access to everything."
- match: {labels: {"role": "administrator"}}
actions: ["*"]
comment: "Full registry access"
- match:
labels: {"role": "developer"}
name: "dev-${labels:team}/*"
actions: ["push", "pull"]
comment: "Team-specific write access"
- match: { account: "" }
actions: ["pull"]
comment: "Anonymous users can pull everything."
This is just a basic config file to showcase some of the features, with static users, but Github / Google / LDAP and other auth backends are possible.
More about options supported on this config file can be found in reference config file.
Generate SSL Cert for JWT encryption
Before starting the services, generate an SSL certificate which is used for JWT encryption. The server.pem
needs to be mounted into both auth-server
and registry
, and the server.key
(private key) needs to be mounted into auth-server
mkdir -p conf/keys
openssl req -newkey rsa:4096 -nodes -sha256 \
-keyout conf/keys/server.key \
-x509 -days 3650 -out conf/keys/server.pem \
-subj "/CN=docker-registry-auth-server-jwt-signer"
Caddy Reverse Proxy Configuration
For HTTPS with a certificates managed by LetsEncrypt - I've picked Caddy, as that is most simple way to get it working. Also, Caddyfile allows to use environment variables, so it's easy to adapt to your needs without ever touching the Caddyfile itself.
In the past, I had TLS setup handled by Traefik, or Nginx Ingress Controller & Cert-manager on K8S, but for this tutorial - Caddy is simply the easiest solution.
# conf/Caddyfile
{$AUTH_DOMAIN} {
reverse_proxy auth:5001
}
{$REGISTRY_DOMAIN} {
reverse_proxy registry:5000
}
Setting Up Environment Variables
Create an .env
file with domain names. These domain names would be used in the Caddy configuration to handle the TLS.
# .env
AUTH_DOMAIN=auth.yourdomain.com
REGISTRY_DOMAIN=registry.yourdomain.com
Running the Setup
Run the following command:
docker compose up -d
Caddy will automatically manage TLS certificates, and the authentication service will control user access to the registry.
Usage Examples
Push Image with Admin Rights
docker login registry.yourdomain.com -u admin
docker tag nginx registry.yourdomain.com/library/nginx:latest
docker push registry.yourdomain.com/library/nginx:latest
Pull Image as Developer
docker login registry.yourdomain.com -u developer
docker pull registry.yourdomain.com/library/nginx:latest
Comparison with Basic htpasswd Setup
Feature | Token Auth Setup | Basic htpasswd |
---|---|---|
Granular Permissions | ✅ Per-repo policies | ❌ All-or-nothing |
Audit Logging | ✅ Detailed operations | ❌ Limited visibility |
Token Expiration | ✅ Configurable TTL | ❌ Permanent sessions |
External Auth Integration | ✅ LDAP/OAuth support | ❌ Local users only |
This setup provides robust security and flexibility for teams managing container workloads. The complete code is available on GitHub. Additionally, the repository includes support for Minio as an alternative to S3.