MinIO como Backend de Almacenamiento para Docker Registry

MinIO es un sistema de almacenamiento de objetos compatible con la API de S3 que puedes usar como backend de almacenamiento para Docker Registry, reemplazando el almacenamiento local con una solución escalable y redundante. Esta combinación permite centralizar el almacenamiento de imágenes de contenedores con alta disponibilidad, recolección de basura automatizada y soporte TLS, manteniendo todo en tu propia infraestructura.

Requisitos Previos

  • Servidor Linux con Docker y Docker Compose
  • Mínimo 4 GB de RAM (8 GB recomendado para producción)
  • Almacenamiento suficiente para las imágenes Docker
  • Dominio con HTTPS para el registro
  • Acceso root o sudo

Instalación de MinIO

# Crear directorios de trabajo
mkdir -p /opt/minio/{data,config}
cd /opt/minio

# Descargar MinIO (instalación directa)
wget https://dl.min.io/server/minio/release/linux-amd64/minio \
  -O /usr/local/bin/minio
chmod +x /usr/local/bin/minio

# Descargar el cliente MinIO
wget https://dl.min.io/client/mc/release/linux-amd64/mc \
  -O /usr/local/bin/mc
chmod +x /usr/local/bin/mc

# Verificar instalación
minio --version
mc --version

Crea el servicio systemd para MinIO:

# Crear usuario dedicado
sudo useradd -r -s /sbin/nologin minio
sudo mkdir -p /data/minio
sudo chown minio:minio /data/minio

sudo tee /etc/systemd/system/minio.service << 'EOF'
[Unit]
Description=MinIO Object Storage
After=network.target

[Service]
Type=notify
User=minio
Group=minio
EnvironmentFile=/etc/minio/minio.env
ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES \
  --console-address ":9001"
Restart=always
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

# Crear fichero de variables de entorno
sudo mkdir -p /etc/minio
sudo tee /etc/minio/minio.env << 'EOF'
# Credenciales de acceso a MinIO
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=CAMBIA_ESTA_CONTRASEÑA_SEGURA

# Ruta de almacenamiento
MINIO_VOLUMES="/data/minio"

# URL pública (para generación de URLs pre-firmadas)
MINIO_SERVER_URL=https://s3.tudominio.com
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now minio

# Verificar que está funcionando
sudo systemctl status minio
curl http://localhost:9000/minio/health/live

Configuración del Bucket para Docker Registry

Configura el cliente mc y crea el bucket:

# Configurar el cliente mc apuntando a MinIO
mc alias set local http://localhost:9000 minioadmin TU_CONTRASEÑA

# Verificar la conexión
mc admin info local

# Crear el bucket para Docker Registry
mc mb local/docker-registry

# Configurar el bucket como privado (no acceso público)
mc anonymous set none local/docker-registry

# Crear usuario específico para Docker Registry (mejor práctica de seguridad)
mc admin user add local registryuser CONTRASEÑA_REGISTRY_USER

# Crear política de acceso al bucket
mc admin policy create local registry-policy /dev/stdin << '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

# Asignar la política al usuario del registro
mc admin policy attach local registry-policy --user registryuser

Despliegue de Docker Registry con MinIO

mkdir -p /opt/docker-registry
cd /opt/docker-registry

# Crear configuración del registro
tee config.yml << 'EOF'
version: 0.1

log:
  level: info
  formatter: json

storage:
  s3:
    accesskey: registryuser
    secretkey: CONTRASEÑA_REGISTRY_USER
    regionendpoint: http://localhost:9000
    bucket: docker-registry
    region: us-east-1       # Valor requerido aunque MinIO no lo use
    encrypt: false
    secure: false            # True si MinIO usa HTTPS
    v4auth: true
    chunksize: 5242880       # 5MB en bytes
    rootdirectory: /registry

  delete:
    enabled: true            # Habilitar borrado de capas/manifiestos

  maintenance:
    uploadpurging:
      enabled: true
      age: 168h              # Limpiar uploads incompletos después de 7 días
      interval: 24h
      dryrun: false

http:
  addr: :5000
  secret: GENERA_UN_SECRET_ALEATORIO_AQUI

auth:
  htpasswd:
    realm: "Docker Registry"
    path: /auth/htpasswd
EOF

# Crear directorio de autenticación
mkdir -p /opt/docker-registry/auth

# Crear usuario de acceso al registro
docker run --rm --entrypoint htpasswd \
  httpd:2 -Bbn admin CONTRASEÑA_ADMIN \
  > /opt/docker-registry/auth/htpasswd

# Docker Compose para el registro
tee docker-compose.yml << 'EOF'
version: "3.8"

services:
  registry:
    image: registry:2
    container_name: docker-registry
    restart: unless-stopped
    ports:
      - "5000:5000"
    environment:
      REGISTRY_HTTP_ADDR: 0.0.0.0:5000
    volumes:
      - ./config.yml:/etc/docker/registry/config.yml:ro
      - ./auth:/auth:ro
EOF

docker compose up -d
docker compose logs -f registry

Configuración TLS

Configura HTTPS para el registro Docker:

# Obtener certificado SSL para el registro
sudo certbot certonly --standalone \
  -d registry.tudominio.com

# Actualizar docker-compose.yml para usar TLS
tee /opt/docker-registry/docker-compose.yml << 'EOF'
version: "3.8"

services:
  registry:
    image: registry:2
    container_name: docker-registry
    restart: unless-stopped
    ports:
      - "443:5000"
    volumes:
      - ./config.yml:/etc/docker/registry/config.yml:ro
      - ./auth:/auth:ro
      - /etc/letsencrypt/live/registry.tudominio.com:/certs:ro
    environment:
      REGISTRY_HTTP_TLS_CERTIFICATE: /certs/fullchain.pem
      REGISTRY_HTTP_TLS_KEY: /certs/privkey.pem
EOF

docker compose up -d

# Probar el registro con TLS
docker login registry.tudominio.com
docker pull ubuntu:22.04
docker tag ubuntu:22.04 registry.tudominio.com/ubuntu:22.04
docker push registry.tudominio.com/ubuntu:22.04

Recolección de Basura

Docker Registry almacena capas huérfanas cuando se borran imágenes. Ejecuta el recolector de basura regularmente:

# Ver el espacio usado antes de la limpieza
mc du local/docker-registry

# Listar manifiestos en el registro (para referencia)
curl -u admin:CONTRASEÑA \
  https://registry.tudominio.com/v2/_catalog

# Ejecutar recolección de basura en modo dry-run primero
docker exec docker-registry registry garbage-collect \
  --dry-run /etc/docker/registry/config.yml

# Ejecutar la recolección de basura real
# IMPORTANTE: El registro debe estar en modo solo-lectura o parado
docker exec docker-registry registry garbage-collect \
  --delete-untagged /etc/docker/registry/config.yml

# Automatizar con cron (ejecutar en modo solo-lectura)
sudo tee /etc/cron.weekly/registry-gc << 'EOF'
#!/bin/bash
# Poner el registro en modo solo lectura
docker exec docker-registry \
  sed -i 's/# readonly: false/readonly: true/' /etc/docker/registry/config.yml

# Ejecutar garbage collection
docker exec docker-registry registry garbage-collect \
  --delete-untagged /etc/docker/registry/config.yml

# Restaurar modo escritura
docker exec docker-registry \
  sed -i 's/readonly: true/# readonly: false/' /etc/docker/registry/config.yml

# Reiniciar el registro
docker restart docker-registry
EOF
chmod +x /etc/cron.weekly/registry-gc

Alta Disponibilidad

Para entornos de producción con alta disponibilidad:

# docker-compose-ha.yml - Múltiples réplicas del registro
version: "3.8"

services:
  registry-1:
    image: registry:2
    volumes:
      - ./config.yml:/etc/docker/registry/config.yml:ro
      - ./auth:/auth:ro

  registry-2:
    image: registry:2
    volumes:
      - ./config.yml:/etc/docker/registry/config.yml:ro
      - ./auth:/auth:ro

  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - registry-1
      - registry-2

Configuración de MinIO en modo distribuido para HA:

# MinIO en modo distribuido con 4 nodos
# En cada nodo, crear el mismo servicio apuntando a todos
MINIO_ROOT_USER=admin \
MINIO_ROOT_PASSWORD=contraseña \
minio server \
  http://nodo1.tudominio.com/data \
  http://nodo2.tudominio.com/data \
  http://nodo3.tudominio.com/data \
  http://nodo4.tudominio.com/data \
  --console-address ":9001"

Solución de Problemas

Error "blob unknown" al hacer pull:

# Verificar que el bucket existe y tiene los datos
mc ls local/docker-registry/registry/

# Comprobar permisos del usuario del registro en MinIO
mc admin policy ls local
mc admin user info local registryuser

Error de autenticación en el registro:

# Probar las credenciales del htpasswd
docker run --rm --entrypoint htpasswd \
  httpd:2 -v /tmp/htpasswd admin

# Ver los logs del registro para más detalles
docker logs docker-registry --tail 50

Recolección de basura no libera espacio:

# Verificar que las imágenes están realmente desreferenciadas
# Primero borrar los tags manualmente
curl -X DELETE -u admin:CONTRASEÑA \
  "https://registry.tudominio.com/v2/imagen/manifests/$(
    curl -u admin:CONTRASEÑA \
      "https://registry.tudominio.com/v2/imagen/manifests/latest" \
      -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
      -I | grep Docker-Content-Digest | awk '{print $2}'
  )"

Conclusión

La combinación de MinIO y Docker Registry proporciona una solución de almacenamiento de imágenes de contenedores completamente autoalojada, escalable y compatible con los flujos de trabajo estándar de Docker. MinIO ofrece la capacidad de crecer horizontalmente con el tiempo, mientras que Docker Registry mantiene compatibilidad total con el ecosistema de herramientas existente. Implementa la recolección de basura regularmente para mantener el uso del almacenamiento bajo control.