Por qué un registro Docker privado con Harbor
Docker Hub y GHCR son servicios públicos convenientes, pero plantean tres problemas reales a medida que tu organización crece o debe cumplir con NIS2 (art. 21 § 2 f – seguridad de la cadena de suministro) o GDPR (datos de infraestructura alojados fuera de la UE). Harbor aborda cada punto directamente.
Beneficios clave de Harbor
- Escaneo Trivy integrado — cada push dispara un análisis CVE; las políticas de admisión bloquean el despliegue de imágenes que superen el umbral de criticidad configurado.
- Firma Cosign / Notary — cadena de confianza desde el build CI hasta el clúster Kubernetes; solo pasan las imágenes firmadas con tu clave.
- RBAC multi-proyecto — aislamiento por equipo, entorno (dev/staging/prod) o cliente, con roles Guest, Developer, Maintainer y ProjectAdmin.
- Replicación entre registros — sincroniza con un segundo VPS, AWS ECR o Docker Hub para distribución CDN.
- Logs de auditoría inmutables — cada pull, push, eliminación e inicio de sesión queda registrado con la IP de origen; conformidad con NIS2 artículo 21 § 2 d.
- Sin límite de pulls — tus runners CI descargan tantas imágenes como necesiten sin retrasos ni cuotas.
- OIDC nativo — conecta Harbor a ZITADEL o cualquier proveedor OpenID Connect; cero cuentas locales que gestionar.
Prerrequisitos cuantificados
Harbor funciona cómodamente en un VPS con la siguiente configuración:
- CPU: 4 vCPU (mínimo 2 vCPU, pero Trivy consume 1–2 núcleos durante el escaneo)
- RAM: 8 GB (mínimo 4 GB; reserva 2 GB para PostgreSQL, 1 GB para Redis, 2 GB para Harbor core, 1 GB de margen)
- Disco: 100 GB SSD (las capas Docker se acumulan rápido; reserva 40 GB para el registro, 20 GB para los logs de Trivy, 20 GB para el sistema)
- SO: Debian 12 o Ubuntu 22.04 LTS
- Docker: ≥ 24.0, Docker Compose: ≥ 2.20
- DNS: un subdominio apuntando a tu VPS (registry.ejemplo.com), ya resolviendo
- TLS: certificado válido — Let's Encrypt via Certbot o Traefik ACME
Instalación de Harbor v2.15 con Docker Compose
Descargar el instalador offline
Conéctate por SSH a tu VPS. Descarga el paquete offline Harbor v2.15.4 (el más estable a 2026-10-13) desde GitHub:
export HARBOR_VERSION=v2.15.4 wget https://github.com/goharbor/harbor/releases/download/${HARBOR_VERSION}/harbor-offline-installer-${HARBOR_VERSION}.tgz wget https://github.com/goharbor/harbor/releases/download/${HARBOR_VERSION}/harbor-offline-installer-${HARBOR_VERSION}.tgz.asc # Verificar la firma GPG gpg --keyserver keyserver.ubuntu.com --recv-keys 7722D168DAEC457894C2988818B80B4B2D318C8A gpg --verify harbor-offline-installer-${HARBOR_VERSION}.tgz.asc tar xvf harbor-offline-installer-${HARBOR_VERSION}.tgz cd harborPreparar los certificados TLS
Harbor requiere TLS. Con Certbot:
apt install -y certbot certbot certonly --standalone -d registry.ejemplo.com # Los certificados están en /etc/letsencrypt/live/registry.ejemplo.com/ mkdir -p /data/cert cp /etc/letsencrypt/live/registry.ejemplo.com/fullchain.pem /data/cert/registry.ejemplo.com.crt cp /etc/letsencrypt/live/registry.ejemplo.com/privkey.pem /data/cert/registry.ejemplo.com.keySi usas Traefik como reverse proxy (ver el artículo de Traefik v3), delega la terminación TLS a Traefik y configura Harbor en HTTP interno en el puerto 8080.
Configurar harbor.yml
Copia la plantilla y ajusta los valores esenciales:
cp harbor.yml.tmpl harbor.ymlEdita
harbor.yml:hostname: registry.ejemplo.com https: port: 443 certificate: /data/cert/registry.ejemplo.com.crt private_key: /data/cert/registry.ejemplo.com.key harbor_admin_password: CambiaEstaContrasena! database: password: CambiaEstasTambien! data_volume: /data trivy: ignore_unfixed: false skip_update: false offline_scan: false security_check: vuln insecure: falseEjecutar el instalador
Harbor ofrece componentes opcionales. Activa Trivy (escáner) y Notary (firmas):
./install.sh --with-trivy --with-notaryEl instalador genera un
docker-compose.ymly arranca todos los contenedores. Verifica que todos esténhealthy:docker compose ps # STATUS esperado para cada servicio: healthyPrimer inicio de sesión y cambio de contraseña
Abre
https://registry.ejemplo.comen tu navegador. Inicia sesión conadmin/ la contraseña deharbor.yml. Ve a Administration → Users → admin y cambia la contraseña inmediatamente.Desde tu estación de trabajo o runner CI:
docker login registry.ejemplo.com # Introduce tus credenciales de admin o una cuenta de servicioCrear un proyecto y hacer push de la primera imagen
En la interfaz de Harbor, crea un proyecto
mi-proyecto(privado). Luego desde tu máquina de build:docker pull nginx:alpine docker tag nginx:alpine registry.ejemplo.com/mi-proyecto/nginx:1.27-alpine docker push registry.ejemplo.com/mi-proyecto/nginx:1.27-alpine # En Harbor → mi-proyecto → Repositories: la imagen aparece con su estado TrivyHabilitar la renovación automática de TLS
Crea un hook post-renovación de Certbot para recargar Harbor:
cat > /etc/letsencrypt/renewal-hooks/post/harbor-reload.sh << 'EOF' #!/bin/bash cp /etc/letsencrypt/live/registry.ejemplo.com/fullchain.pem /data/cert/registry.ejemplo.com.crt cp /etc/letsencrypt/live/registry.ejemplo.com/privkey.pem /data/cert/registry.ejemplo.com.key cd /opt/harbor && docker compose restart proxy EOF chmod +x /etc/letsencrypt/renewal-hooks/post/harbor-reload.sh
Configuración: proyectos, usuarios y OIDC (ZITADEL)
Proyectos y RBAC
Harbor organiza las imágenes por proyectos. Cada proyecto puede ser público o privado, con una política de retención independiente. Crea un proyecto por equipo o entorno: frontend, backend, infra. Asigna miembros con roles granulares:
- Guest: solo pull
- Developer: push + pull
- Maintainer: gestión de tags, webhooks y políticas de retención
- ProjectAdmin: gestión de miembros
Integración OIDC con ZITADEL
Si ya has desplegado ZITADEL (ver el artículo dedicado), conectar Harbor es cuestión de unos pocos clics. En ZITADEL, crea una aplicación harbor de tipo *Web*, anota el Client ID y el Client Secret. En Harbor → Administration → Configuration → Authentication:
- Auth Mode: OIDC
- OIDC Provider Name: ZITADEL
- OIDC Endpoint: https://sso.ejemplo.com
- OIDC Client ID / Secret: los de ZITADEL
- OIDC Scope: openid profile email groups
- Marca Verify Certificate
En el primer inicio de sesión OIDC, Harbor crea automáticamente la cuenta de usuario. Luego desactiva la creación de cuentas locales (Administration → Configuration → Allow Self-Registration → Off).
Escanear imágenes con Trivy
Harbor embebe Trivy directamente en el pipeline de push. En cuanto se hace push de una imagen, Harbor lanza un escaneo asíncrono que consulta la base CVE de Trivy (actualizada diariamente). El resultado aparece en la interfaz: número de vulnerabilidades por gravedad (CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN).
Activar el escaneo automático en push: en Harbor → Administration → Configuration → Vulnerability → marca *Automatically scan images on push*.
Consultar un informe: abre un repositorio, haz clic en un tag → pestaña *Vulnerabilities*. Cada CVE lista el paquete afectado, la versión corregida y un enlace a la NVD.
Versión de Trivy en Harbor v2.15: Harbor v2.15.4 incluye trivy-adapter alineado con Trivy v0.55.x, con soporte para bases OS (Alpine, Debian, RHEL, Ubuntu), gestores de paquetes de aplicación (npm, pip, cargo, go.mod) y archivos de lock.
Políticas de conformidad por proyecto: en Harbor → *Tu proyecto* → Policy → *Deployment Security* → elige el nivel de gravedad que bloquea el pull. Por ejemplo, bloquear cualquier pull de una imagen con al menos una CVE CRITICAL no corregida.
Consejo de seguridad: políticas de admisión en Kubernetes
Combina Harbor con el controlador de admisión Cosign (vía Kyverno o Connaisseur) para que ninguna imagen sin firmar o con una CVE CRITICAL pueda entrar en tu clúster. En tu pipeline CI:
# Firmar la imagen con Cosign después del build
cosign sign --key cosign.key registry.ejemplo.com/mi-proyecto/nginx:1.27-alpine
# Verificar en CI antes del despliegue
cosign verify --key cosign.pub registry.ejemplo.com/mi-proyecto/nginx:1.27-alpineConfigura una política Kyverno que exija la firma de Harbor: cualquier pod cuya imagen no esté firmada por tu clave es rechazado en la admisión. Esto cierra la brecha más explotada en los ataques a la cadena de suministro tras CVE-2026-21589.
Solución de problemas: 5 errores reales con sus causas
1. Error response from daemon: Get https://registry.ejemplo.com/v2/: x509: certificate signed by unknown authority
Causa: el certificado Let's Encrypt no se ha propagado aún o estás usando un certificado auto-firmado.
Solución: en cada nodo Docker cliente, copia fullchain.pem a /etc/docker/certs.d/registry.ejemplo.com/ca.crt y luego systemctl restart docker.
2. harbor-core reiniciando en bucle con Error 1045 (28000): Access denied for user 'root'@'localhost'
Causa: el volumen PostgreSQL contiene datos de una instalación anterior con una contraseña diferente.
Solución: docker compose down -v (⚠️ borra los datos), modifica la contraseña en harbor.yml, vuelve a ejecutar ./prepare && docker compose up -d.
3. Escaneo Trivy bloqueado en Fetching vulnerability DB durante más de 10 minutos
Causa: el VPS no puede acceder a ghcr.io (firewall de salida restrictivo) para descargar la base de Trivy.
Solución: permite la salida HTTPS hacia ghcr.io y pkg.dev, o activa offline_scan: true tras precargar la base con trivy image --download-db-only.
4. unauthorized: unauthorized to access repository en CI pese a un docker login exitoso
Causa: la cuenta de servicio no tiene el rol Developer en el proyecto destino.
Solución: en Harbor → *Tu proyecto* → Members → añade la cuenta robot o usuario con el rol Developer o superior.
5. Interfaz de Harbor inaccesible tras la renovación del certificado Let's Encrypt
Causa: el contenedor proxy (nginx) sigue sirviendo el antiguo certificado cargado en memoria.
Solución: docker compose restart proxy desde la carpeta de instalación de Harbor. Lo ideal es automatizarlo mediante el hook post-renovación descrito en el paso 7.
Harbor vs Docker Hub vs GHCR vs Gitea Registry
Desplace la tabla
| Criterio | Harbor self-hosted | Docker Hub | GHCR | Gitea Registry |
|---|---|---|---|---|
| Alojamiento | Tu VPS (datos en la UE) | Servidores Docker Inc. (EE.UU.) | Servidores GitHub/Microsoft (EE.UU./UE opt.) | Tu VPS |
| Escaneo de vulnerabilidades | Trivy integrado, auto en push | Docker Scout (de pago) | Ninguno nativo | Ninguno nativo |
| Firma de imágenes | Cosign + Notary v2 | DCT (obsoleto) | Cosign externo | Ninguno nativo |
| RBAC granular | 5 roles por proyecto | Roles de organización limitados | Roles GitHub (gruesos) | Roles básicos de Gitea |
| Límite de pulls | Ninguno | 100/6h (gratis), 5000/día (Pro) | Vinculado a límites de GitHub | Ninguno |
| OIDC/SSO nativo | Sí (LDAP, OIDC, SAML) | No (SSO empresarial de pago) | Solo SSO de GitHub | OIDC parcial |
| Logs de auditoría completos | Sí (IP, usuario, acción, tag) | Limitado | Parcial vía auditoría GitHub | Limitado |
| Conformidad NIS2/GDPR | Total (datos on-premise) | Solo contractual | DPA Microsoft disponible | Total (on-premise) |
| Coste | VPS (~15–30 €/mes) | Gratis / Pro ~5$/mes | Incluido en GitHub Free/Pro | VPS compartido con Gitea |