Secure Docker Registry Setup with TLS, Authentication, and UI π
In this blog post, we will set up a secure Docker Registry enhanced with TLS encryption, HTTP authentication, and a user-friendly web interface (registry-ui) developed by Joxit. Special thanks to Joxit for creating a clean and intuitive UI for Docker registries.
Docker Registry is an open-source solution provided by Docker to store Docker images privately. While alternatives like Harbor offer richer features such as role-based access control, vulnerability scanning, and advanced user management, Docker Registry excels in simplicity and efficiency. It's perfect for lightweight and resource-friendly setups without compromising security.
All the necessary code and configurations used in this blog are available on my GitHub repository.
π What You Need
After completing this setup, you'll have:
- A Docker Registry with basic authentication.
- TLS encryption with SSL certificates.
- Docker Registry and UI managed via Docker Compose.
- Ability to manage Docker images through an intuitive web UI and Docker CLI.
π Project Directory Structure
Before we start, organize your project folder clearly as shown below:
PrivateRegistry/
βββ auth/
β βββ registry.passwd
βββ nginx/
β βββ nginx.conf
β βββ ssl/
β βββ fullchain.pem
β βββ privkey.pem
βββ registry_data/
βββ docker-compose.yml
Disk Space Planning
Ensure that the server has enough disk space allocated for registry_data/
, where Docker images will be stored.
Docker images, especially in production, can grow significantly over time. Plan your storage accordingly based on expected image size and frequency of updates.
π Step-by-Step Setup
Step 1: Create Authentication Credentials π
Create authentication credentials using htpasswd
. Replace username
with your desired registry username:
Step 2: Generate SSL Certificate π
You must secure your Docker Registry with SSL. For this, we will use Certbot to generate a trusted SSL certificate. Example domain: repo.ruchan.dev
(Replace this with your own domain)
sudo apt install certbot
sudo certbot certonly --standalone -d repo.ruchan.dev
Certbot generates:
- Fullchain certificate:
/etc/letsencrypt/live/repo.ruchan.dev/fullchain.pem
- Private key:
/etc/letsencrypt/live/repo.ruchan.dev/privkey.pem
Copy these files into your project's nginx/ssl/
directory:
mkdir -p nginx/ssl/
sudo cp /etc/letsencrypt/live/repo.ruchan.dev/fullchain.pem nginx/ssl/
sudo cp /etc/letsencrypt/live/repo.ruchan.dev/privkey.pem nginx/ssl/
Self-signed Certificates
If using self-signed or untrusted certificates, copy the fullchain.pem
to your Docker hosts:
sudo mkdir -p /etc/docker/certs.d/repo.ruchan.dev:443/
sudo cp fullchain.pem /etc/docker/certs.d/repo.ruchan.dev:443/ca.crt
sudo systemctl restart docker
Trusted certificates generated via Certbot do not require these extra steps.
Step 3: Docker Compose Configuration π οΈ
Here's the complete docker-compose.yml
file integrating registry, UI, and Nginx with SSL:
version: '3'
services:
registry:
image: registry:latest
container_name: registry
hostname: registry
restart: always
environment:
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry-Realm
REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.passwd
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
REGISTRY_STORAGE_DELETE_ENABLED: 'true'
volumes:
- ./registry_data:/data
- ./auth:/auth
networks:
- docker
nginx:
image: nginx:mainline-alpine
container_name: nginx_registry
restart: unless-stopped
tty: true
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/ssl/:/etc/nginx/ssl/
networks:
- docker
depends_on:
- registry
registry-ui:
image: joxit/docker-registry-ui:main
hostname: registry-ui
restart: always
environment:
- SINGLE_REGISTRY=true
- UI_ROOT_PATH=/ui
- REGISTRY_TITLE=Docker Registry UI
- DELETE_IMAGES=true
- SHOW_CONTENT_DIGEST=true
- NGINX_PROXY_PASS_URL=http://registry:5000
container_name: registry-ui
networks:
- docker
networks:
docker:
driver: bridge
Storage Directory Best Practice
For production environments, instead of using a local ./registry_data folder, consider mounting a dedicated disk volume or using external storage for /data
.
Example change in docker-compose.yml
:
This ensures persistence and scalability for large Docker image repositories.
Domain Configuration
Replace repo.ruchan.dev
in the above configurations with your own domain.
Step 4: Nginx Configuration π
Place this configuration into nginx/nginx.conf
(replace repo.ruchan.dev
with your domain):
events {}
http {
upstream docker-registry {
server registry:5000;
}
upstream registry-ui {
server registry-ui:80;
}
server {
listen 443 ssl;
server_name repo.ruchan.dev;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
location /ui/ {
proxy_pass http://registry-ui/;
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;
}
location / {
client_max_body_size 0;
add_header 'Access-Control-Allow-Origin' 'https://repo.ruchan.dev' always;
add_header 'Access-Control-Allow-Methods' 'HEAD, GET, OPTIONS, DELETE' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Accept, Cache-Control' always;
proxy_pass http://docker-registry;
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;
}
}
}
UI Path Conflict
The UI is mapped under /ui/
. If you create Docker repositories that start with /ui/
, it can conflict with the UI route and cause problems.
Solutions:
- Map the UI under a different path (e.g., /dashboard/
).
- Host the UI under a different subdomain (e.g., ui.repo.ruchan.dev
).
Start your services:
π¨ Using the Registry UI
Access the Registry UI via:
Here you'll see your Docker images visually.
π’ Docker Push/Pull Operations
Docker Login
Push Images
docker tag my-image:latest repo.ruchan.dev/my-image:latest
docker push repo.ruchan.dev/my-image:latest
Pull Images
π― Conclusion
Docker Registry combined with authentication, TLS encryption, and registry-ui offers a secure yet simple solution for managing Docker images privately. While it lacks advanced Harbor-like features, it's lightweight, resource-efficient, and secure for many use cases.
Happy containerizing! π
Written by: Ruchan YalΓ§Δ±n | GitHub
π’ Production Environment Recommendations
Although this setup is suitable for development and small-scale production, for a more reliable production-ready deployment, consider the following improvements:
π 1. Implement Regular Backups
Docker images stored in the registry are critical assets and must be protected.
- What to do: Regularly back up the
registry_data/
directory to external storage or cloud solutions. - Suggestions:
- Local disk backups (e.g., external drives)
- Network Attached Storage (NAS)
- Cloud storage solutions (e.g., AWS S3, Google Cloud Storage, Backblaze B2)
These methods ensure that even in case of server failure, your images remain safe.
π 2. Automate TLS Certificate Renewal
Certbot certificates expire every 90 days.
- What to do: Set up automatic certificate renewal and reload Nginx after renewal.
- Suggestions:
- Use
certbot renew
command periodically. - Automate the process with scheduled tasks (e.g., cron jobs).
This ensures your site stays secure without manual intervention.
β‘ 3. Plan for High Availability (Optional)
For mission-critical setups:
- Deploy multiple registry
instances behind an Nginx load balancer.
- Use a shared storage solution (like NFS or S3-backed storage).
- Use a health check system for containers.
Even a simple backup docker-compose
stack in another server can increase resilience.
π‘οΈ 4. Enhance Security Settings
- Nginx Enhancements:
- Add rate limiting to protect against brute-force attacks.
-
Restrict allowed IPs or authentication methods if necessary.
-
Example Basic Rate Limit in
nginx.conf
:Add inside server blocklimit_req_zone $binary_remote_addr zone=one:10m rate=5r/s; location / { limit_req zone=one burst=10; proxy_pass http://docker-registry; 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; }
-
Auth Hardening: Rotate your
htpasswd
credentials periodically.
By following these practices, you can make your Docker Registry setup much more robust, scalable, and production-friendly! π
Happy containerizing! π‘οΈ