Minio as Docker Registry Storage Backend
MinIO provides S3-compatible object storage that works as a scalable, high-availability backend for Docker Registry, replacing local filesystem storage. This guide covers configuring Docker Registry with MinIO as the S3 storage backend, including TLS, garbage collection, and a high-availability deployment.
Prerequisites
- Ubuntu 20.04+/Debian 11+ or CentOS 8+/Rocky Linux 8+
- Docker and Docker Compose installed
- 4+ GB available disk space for MinIO
- Root or sudo access
Installing MinIO
Deploy MinIO via Docker (quickest method):
mkdir -p /opt/minio/{data,config}
docker run -d \
--name minio \
-p 9000:9000 \
-p 9001:9001 \
-v /opt/minio/data:/data \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin_secret \
--restart unless-stopped \
quay.io/minio/minio server /data --console-address ":9001"
Or install the binary directly:
# Download MinIO
wget https://dl.min.io/server/minio/release/linux-amd64/minio -O /usr/local/bin/minio
chmod +x /usr/local/bin/minio
# Create a dedicated user
useradd -r -s /sbin/nologin minio-user
mkdir -p /var/lib/minio
chown minio-user:minio-user /var/lib/minio
# Create environment file
cat > /etc/minio.env << 'EOF'
MINIO_VOLUMES="/var/lib/minio"
MINIO_OPTS="--console-address :9001"
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin_secret
EOF
# Create systemd service
tee /etc/systemd/system/minio.service << 'EOF'
[Unit]
Description=MinIO Object Storage
After=network.target
[Service]
Type=simple
EnvironmentFile=/etc/minio.env
ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES $MINIO_OPTS
User=minio-user
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now minio
Creating a MinIO Bucket for Registry
Use the MinIO console at http://your-server:9001 or the mc CLI:
# Install MinIO Client
wget https://dl.min.io/client/mc/release/linux-amd64/mc -O /usr/local/bin/mc
chmod +x /usr/local/bin/mc
# Configure mc alias
mc alias set local http://localhost:9000 minioadmin minioadmin_secret
# Create the registry bucket
mc mb local/docker-registry
# Set the bucket policy (private by default - correct for a registry)
mc anonymous set none local/docker-registry
# Create a dedicated service account for the registry
mc admin user add local registry-user registry_secret_123
# Create policy for registry access
cat > /tmp/registry-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:ListBucket",
"s3:ListBucketMultipartUploads"
],
"Resource": ["arn:aws:s3:::docker-registry"]
},
{
"Effect": "Allow",
"Action": [
"s3:AbortMultipartUpload",
"s3:DeleteObject",
"s3:GetObject",
"s3:ListMultipartUploadParts",
"s3:PutObject"
],
"Resource": ["arn:aws:s3:::docker-registry/*"]
}
]
}
EOF
mc admin policy create local registry-policy /tmp/registry-policy.json
mc admin policy attach local registry-policy --user registry-user
Configuring Docker Registry with MinIO
Create the Docker Registry configuration:
mkdir -p /opt/registry
cat > /opt/registry/config.yml << 'EOF'
version: 0.1
log:
level: info
formatter: text
storage:
s3:
accesskey: registry-user
secretkey: registry_secret_123
regionendpoint: http://localhost:9000 # MinIO endpoint
region: us-east-1 # Any value works with MinIO
bucket: docker-registry
encrypt: false
secure: false # Set true if MinIO has TLS
v4auth: true
chunksize: 5242880 # 5MB chunks
rootdirectory: /registry # Prefix within the bucket
cache:
blobdescriptor: inmemory
delete:
enabled: true # Required for garbage collection
http:
addr: :5000
secret: generate_a_strong_secret_here
auth:
htpasswd:
realm: Registry
path: /auth/htpasswd
EOF
Create registry authentication:
mkdir -p /opt/registry/auth
sudo apt install -y apache2-utils
htpasswd -Bbn registry_user registry_password > /opt/registry/auth/htpasswd
Running Docker Registry
docker run -d \
--name registry \
--restart unless-stopped \
-p 5000:5000 \
-v /opt/registry/config.yml:/etc/docker/registry/config.yml \
-v /opt/registry/auth:/auth \
registry:2
Or with Docker Compose:
# /opt/registry/docker-compose.yml
version: '3.8'
services:
registry:
image: registry:2
container_name: registry
ports:
- "127.0.0.1:5000:5000"
volumes:
- ./config.yml:/etc/docker/registry/config.yml
- ./auth:/auth
restart: unless-stopped
cd /opt/registry
docker compose up -d
docker compose logs -f registry
Test the registry:
# Login
docker login localhost:5000 -u registry_user -p registry_password
# Push a test image
docker pull alpine
docker tag alpine localhost:5000/alpine:test
docker push localhost:5000/alpine:test
# Verify it's in MinIO
mc ls local/docker-registry/registry/
TLS Configuration
For production, configure TLS on MinIO and update the registry config:
# Copy certificates to MinIO's config directory
mkdir -p /root/.minio/certs
cp /etc/letsencrypt/live/storage.yourdomain.com/fullchain.pem /root/.minio/certs/public.crt
cp /etc/letsencrypt/live/storage.yourdomain.com/privkey.pem /root/.minio/certs/private.key
# Restart MinIO to pick up TLS
systemctl restart minio
Update the registry config to use HTTPS:
storage:
s3:
regionendpoint: https://storage.yourdomain.com
secure: true
For the Docker Registry itself, configure TLS via Nginx:
server {
listen 443 ssl;
server_name registry.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/registry.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/registry.yourdomain.com/privkey.pem;
client_max_body_size 0; # Disable size limit for image pushes
location / {
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;
}
}
Garbage Collection
Docker Registry marks deleted layers but doesn't immediately free space. Run garbage collection to reclaim storage in MinIO:
# Run garbage collection (registry must be in read-only mode or stopped)
# First put the registry in maintenance mode
docker exec registry registry garbage-collect \
/etc/docker/registry/config.yml --delete-untagged=true
# Check MinIO storage before and after
mc du local/docker-registry
Automate garbage collection weekly:
# /etc/cron.weekly/registry-gc
#!/bin/bash
docker exec registry registry garbage-collect \
/etc/docker/registry/config.yml --delete-untagged=true
High-Availability Deployment
For HA, run multiple registry containers pointing to the same MinIO backend:
version: '3.8'
services:
registry-1:
image: registry:2
volumes:
- ./config.yml:/etc/docker/registry/config.yml
- ./auth:/auth
ports:
- "127.0.0.1:5001:5000"
restart: unless-stopped
registry-2:
image: registry:2
volumes:
- ./config.yml:/etc/docker/registry/config.yml
- ./auth:/auth
ports:
- "127.0.0.1:5002:5000"
restart: unless-stopped
Load balance with Nginx upstream:
upstream registry {
server 127.0.0.1:5001;
server 127.0.0.1:5002;
}
For MinIO HA, deploy a distributed cluster (4+ nodes recommended).
Troubleshooting
Registry can't connect to MinIO:
# Verify MinIO is accessible from the registry container
docker exec registry curl -v http://host.docker.internal:9000/docker-registry
# Check credentials
mc alias set test http://localhost:9000 registry-user registry_secret_123
mc ls test/docker-registry
Image push fails with "access denied":
# Check the MinIO policy attached to registry-user
mc admin user info local registry-user
mc admin policy list local
"unauthorized: authentication required" on pull:
# Verify htpasswd file
cat /opt/registry/auth/htpasswd
# Test authentication directly
curl -u registry_user:registry_password https://registry.yourdomain.com/v2/
Slow push performance:
Increase MinIO's erasure coding chunk size or deploy MinIO closer to the registry. Check network latency between registry and MinIO with ping.
Conclusion
Using MinIO as a Docker Registry storage backend decouples image storage from the registry instances, enabling horizontal scaling and centralized object storage management. This architecture supports high-availability deployments, easy backup via MinIO's mirroring features, and integration with existing S3-compatible workflows. For production, always enable TLS on both MinIO and the registry, and schedule regular garbage collection runs.


