Cómo montar un registro privado de imágenes Docker
Índice de contenidos
- Puntos clave
- ¿Para qué sirve un registro privado de imágenes?
- El docker-compose.yml del registry
- ¿Cómo añadir autenticación básica con htpasswd?
- Publicar el registro con HTTPS tras un proxy inverso
- Cómo subir y descargar imágenes (push y pull)
- Alternativas con interfaz web: Harbor y Zot
- Preguntas frecuentes
- ¿Por qué mi docker push falla con un error 401 o de autenticación?
- ¿Necesito HTTPS para un registro privado?
- ¿Cómo borro imágenes para recuperar espacio?
- Conclusión
- Fuentes
Un registro privado Docker es tu propio almacén de imágenes: la imagen oficial registry escucha en el puerto 5000, guarda las capas en un volumen y, con autenticación htpasswd en formato bcrypt y HTTPS tras un proxy inverso, te permite hacer push y pull de imágenes sin depender de Docker Hub ni de sus límites de descarga.
Cada docker pull de una imagen pública pasa por Docker Hub, con sus límites de descarga y su dependencia de un tercero. Un registro privado le da la vuelta a eso: montas tu propio almacén de imágenes, controlas quién sube y quién descarga, y sirves las capas desde tu servidor. En esta guía verás cómo levantar la imagen oficial registry con Docker Compose, protegerla con autenticación htpasswd, publicarla con HTTPS tras un proxy inverso y usar los comandos push y pull, además de cuándo dar el salto a Harbor o Zot. La misma guía está disponible en inglés.
Puntos clave
- La imagen oficial
registryimplementa la especificación OCI Distribution y escucha en el puerto 5000; monta un volumen en/var/lib/registrypara que las capas persistan. registry:3.0.0fue la primera versión estable de la línea v3 desdev2.8.3: eliminó los controladores de almacenamientoossyswifty movió la configuración a/etc/distribution/config.yml. La última publicada es v3.1.1 (1 de mayo de 2026).- El registro solo admite credenciales htpasswd en formato bcrypt: si generas el fichero sin la opción
-B, todos los intentos de login fallan. - La autenticación exige cifrado: no puedes usar htpasswd sobre HTTP en claro. En producción, termina el TLS en un proxy inverso como Traefik con Let’s Encrypt.
- Se configura por variables de entorno con prefijo
REGISTRY_(REGISTRY_AUTH,REGISTRY_AUTH_HTPASSWD_PATH,REGISTRY_STORAGE_DELETE_ENABLED), sin tocar ningún fichero de configuración. - Cuando necesites interfaz web, control de acceso por roles, escaneo de vulnerabilidades y firma de imágenes, pasa a Harbor, proyecto graduado de la CNCF.
¿Para qué sirve un registro privado de imágenes?
Un registro es un servicio que almacena y distribuye imágenes de contenedor. Docker Hub es el registro público por defecto, pero no es el único: puedes ejecutar el tuyo dentro de tu red. Un registro privado tiene tres motivos de peso.
El primero es la independencia: alojas tus imágenes internas (la de tu aplicación, tus imágenes base a medida) sin subirlas a un servicio público ni chocar con los límites de descarga anónima de Docker Hub. El segundo es la velocidad: en una red local, un pull desde tu propio registro va por la LAN y no por Internet, lo que acelera despliegues y pipelines de integración continua. El tercero es el control: decides quién autentica, qué se guarda y durante cuánto tiempo.
La pieza base es la imagen oficial registry, mantenida por el proyecto distribution de la CNCF, que implementa la especificación OCI Distribution. Es el mismo motor sobre el que se construyen registros más grandes como Harbor. Antes de seguir necesitas Docker instalado; si aún no lo tienes, repasa cómo instalar Docker en Ubuntu 24.04.
El docker-compose.yml del registry
Empecemos por lo mínimo que funciona: un registro que escucha en el puerto 5000 y guarda las imágenes en un volumen con nombre. Fija siempre una versión concreta de la imagen; nunca uses :latest en producción, porque una actualización silenciosa puede cambiarte el comportamiento sin avisar.
services:
registry:
image: registry:2.8.3
restart: unless-stopped
ports:
- "5000:5000"
environment:
REGISTRY_STORAGE_DELETE_ENABLED: "true"
volumes:
- registry_data:/var/lib/registry
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:5000/v2/"]
interval: 30s
timeout: 5s
retries: 3
volumes:
registry_data:
Levántalo y comprueba que responde la API v2, que es el endpoint que usan docker push y docker pull:
docker compose up -d
curl http://localhost:5000/v2/
Una respuesta {} con código 200 confirma que el registro está sano. El volumen registry_data guarda las capas bajo /var/lib/registry, así que sobrevive a reinicios y recreaciones del contenedor. Usé registry:2.8.3 por ser la línea v2 estable con mayor base instalada; si prefieres la línea nueva, cambia el tag a registry:3.1.1 y ten en cuenta que la v3 movió su configuración a /etc/distribution/config.yml y retiró los controladores oss y swift. El volumen en /var/lib/registry sirve para ambas.
Este registro no tiene autenticación: cualquiera con acceso de red puede subir y descargar. Sirve para una red de confianza, pero para cualquier otro caso hay que cerrarlo.
¿Cómo añadir autenticación básica con htpasswd?
El registro incorpora autenticación básica leyendo un fichero htpasswd. Hay un detalle que hace fallar a mucha gente: solo acepta el formato bcrypt. Si generas las credenciales sin la opción -B, el registro las rechaza y todos los logins fallan. Genera el fichero con la propia imagen de Apache, sin instalar nada en el host:
mkdir -p auth
docker run --rm --entrypoint htpasswd httpd:2 -Bbn miusuario micontrasena > auth/htpasswd
Ahora amplía el servicio para cargar ese fichero y activar la autenticación por variables de entorno. Fíjate en que todo se controla con el prefijo REGISTRY_, sin editar ningún config.yml:
services:
registry:
image: registry:2.8.3
restart: unless-stopped
ports:
- "5000:5000"
environment:
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: "Registro privado"
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_STORAGE_DELETE_ENABLED: "true"
volumes:
- registry_data:/var/lib/registry
- ./auth:/auth:ro
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:5000/v2/"]
interval: 30s
timeout: 5s
retries: 3
volumes:
registry_data:
Hay una regla de seguridad que no puedes saltarte: la autenticación básica exige TLS. La documentación de distribution lo dice sin rodeos: no puedes usar esquemas de autenticación que envíen las credenciales en texto claro sin configurar antes el cifrado. Si expones el registro directamente, tendrías que montar un certificado con REGISTRY_HTTP_TLS_CERTIFICATE y REGISTRY_HTTP_TLS_KEY. En la práctica, es más cómodo dejar que un proxy inverso se encargue del HTTPS, como veremos a continuación. Para tomar decisiones sobre cuánta memoria y CPU dar al servicio y cómo revisar sus logs, apóyate en la guía de límites de recursos y logs en Docker.
Publicar el registro con HTTPS tras un proxy inverso
En cualquier despliegue serio no expones el puerto 5000 al mundo: pones delante un proxy inverso que termina el TLS con un certificado de Let’s Encrypt y reenvía el tráfico al registro por la red interna de Docker. Traefik es la opción natural de esta serie. La idea es quitar el ports del registro (deja de publicarlo directamente) y añadir etiquetas para que Traefik lo enrute:
services:
registry:
image: registry:2.8.3
restart: unless-stopped
environment:
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: "Registro privado"
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
volumes:
- registry_data:/var/lib/registry
- ./auth:/auth:ro
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.registry.rule=Host(`registry.ejemplo.com`)"
- "traefik.http.routers.registry.entrypoints=websecure"
- "traefik.http.routers.registry.tls.certresolver=letsencrypt"
- "traefik.http.services.registry.loadbalancer.server.port=5000"
volumes:
registry_data:
networks:
proxy:
external: true
Con este esquema, el TLS lo resuelve Traefik y el registro solo habla HTTP por la red interna proxy, que nunca sale del host. La combinación de HTTPS en el borde más autenticación htpasswd es la configuración recomendada para un homelab o un equipo pequeño. Si vienes de cero con el proxy, sigue primero cómo instalar Traefik con Docker Compose y reutiliza su red externa. Y si necesitaras exponerlo sin TLS solo para pruebas locales, tendrías que añadir el host a insecure-registries en /etc/docker/daemon.json, algo que no debes hacer nunca en producción.
Cómo subir y descargar imágenes (push y pull)
Con el registro publicado en registry.ejemplo.com, el flujo de trabajo son cuatro pasos. La orden docker tag es la clave: una imagen se sube a tu registro solo si su nombre empieza por el dominio del registro.
- Inicia sesión en tu registro con las credenciales htpasswd:
docker login registry.ejemplo.com. - Etiqueta la imagen local con el prefijo del registro:
docker tag miapp:1.0 registry.ejemplo.com/miapp:1.0. - Sube la imagen etiquetada con
docker push registry.ejemplo.com/miapp:1.0. - Descarga la imagen desde otra máquina con
docker pull registry.ejemplo.com/miapp:1.0.
Para verlo de un tirón, estos son los comandos completos:
docker login registry.ejemplo.com
docker tag miapp:1.0 registry.ejemplo.com/miapp:1.0
docker push registry.ejemplo.com/miapp:1.0
docker pull registry.ejemplo.com/miapp:1.0
El primer login guarda las credenciales en ~/.docker/config.json, así que no tienes que repetirlo en cada push. Si automatizas los despliegues, encadena tu registro con una herramienta de actualización como Watchtower para actualizar contenedores, que puede vigilar tu propio registro y renovar los contenedores cuando publicas una imagen nueva.
Alternativas con interfaz web: Harbor y Zot
La imagen registry es deliberadamente austera: no trae interfaz web, ni usuarios con roles, ni escaneo de vulnerabilidades. Cuando el equipo crece, dos proyectos de la CNCF cubren ese hueco sin salir del ecosistema OCI:
- Harbor es un registro de nivel empresarial que extiende el mismo motor distribution y añade consola web, control de acceso por roles (RBAC), escaneo de vulnerabilidades, firma de imágenes y replicación entre registros. Es un proyecto graduado de la CNCF desde junio de 2020, la máxima madurez que otorga la fundación. Es la elección obvia si necesitas gobernanza y auditoría.
- Zot es un registro ligero, neutral respecto a proveedores y nativo OCI (se ajusta puramente a la especificación OCI Distribution). Incluye interfaz web, es más liviano que Harbor y encaja bien cuando quieres una UI sin la complejidad de una plataforma completa.
La regla práctica: empieza con registry para un uso personal o de equipo pequeño, y migra a Harbor cuando necesites roles, escaneo y firma. Como todos hablan OCI, tus imágenes son portables entre ellos.
Preguntas frecuentes
¿Por qué mi docker push falla con un error 401 o de autenticación?
Casi siempre es el formato del fichero htpasswd. El registro solo acepta bcrypt: si lo generaste sin la opción -B, rechaza todas las credenciales. Regenera el fichero con htpasswd -Bbn y reinicia el contenedor. La segunda causa habitual es no haber hecho docker login en el dominio exacto del registro.
¿Necesito HTTPS para un registro privado?
Sí en cuanto actives autenticación: el registro no admite credenciales en texto claro. Para pruebas en una red de confianza sin TLS puedes añadir el host a insecure-registries en /etc/docker/daemon.json, pero es una excepción para laboratorio, nunca para producción. Lo correcto es terminar el TLS en un proxy inverso.
¿Cómo borro imágenes para recuperar espacio?
Activa el borrado con REGISTRY_STORAGE_DELETE_ENABLED=true, elimina el manifiesto por su digest mediante la API v2 y después ejecuta el recolector de basura con registry garbage-collect /etc/docker/registry/config.yml dentro del contenedor. El borrado del manifiesto solo marca las capas; el espacio se libera en la recolección.
Conclusión
Montar un registro privado Docker es sorprendentemente sencillo: la imagen registry en el puerto 5000, un volumen para las capas, un fichero htpasswd en bcrypt y un proxy inverso que aporte el HTTPS. Con eso tienes un almacén de imágenes propio, rápido en tu red y sin depender de los límites de Docker Hub. Cuando el equipo o los requisitos de seguridad crezcan, Harbor y Zot te esperan con interfaz web sobre el mismo estándar OCI. El siguiente paso lógico es automatizar la renovación de contenedores: repasa Watchtower para actualizar contenedores Docker y cierra el círculo entre publicar una imagen y desplegarla.
Fuentes
Código fuente
Accede a todo el código fuente de este artículo en GitHub.
Ver en GitHub