Fallo al iniciar sesión en registro Docker: soluciones de credenciales y autenticación que funcionan

¿Te fue útil?

Siempre ocurre en el peor momento: estás en una llamada de despliegue, la compilación está en verde y el push de la imagen falla porque docker login no puede autenticarse. Alguien dice “funcionó ayer” con la confianza de quien nunca ha visto caches, keychains o proxies empresariales.

Los fallos de inicio de sesión en registros rara vez son “solo credenciales”. Suelen ser una pequeña guerra civil entre Docker, el almacén de credenciales de tu SO, el esquema de autenticación del registro, la confianza TLS y cualquier cosa que la red corporativa haya decidido hacer esta temporada. Ganemos esa guerra rápido.

Playbook de diagnóstico rápido (hacer esto primero)

Este es el orden de triaje que ahorra tiempo. Está optimizado para recuperar un runner de CI o un portátil que no puede hacer push de imágenes sin adivinar.

1) Confirma qué falló: autenticación, TLS o red

  • Si ves 401 Unauthorized o denied: requested access to the resource is denied: autenticación/alcances/permiso.
  • Si ves x509, certificate signed by unknown authority o tls: handshake failure: cadena de confianza / MITM / SNI.
  • Si ves timeout, no route, proxyconnect o EOF: red/proxy/DNS.
  • Si ves error storing credentials, credentials store is not initialized o exec: docker-credential-*: helper de credenciales/keychain.

2) Identifica qué endpoint del registro estás apuntando

La gente suele escribir el hostname equivocado (o omitirlo), golpeando silenciosamente Docker Hub en lugar del registro privado. Tu terminal no te juzgará; simplemente fallará.

3) Inspecciona la configuración efectiva de credenciales de Docker

Empieza por ~/.docker/config.json. Si hay una entrada credsStore o credHelpers, tu login probablemente esté fallando en el helper, no en el registro.

4) Activa debug del cliente para una ejecución

Usa DOCKER_CLI_DEBUG=1 o los logs del daemon si estás en un host que ejecuta el daemon. La salida de debug aclara qué helper se está llamando y qué endpoint se usa.

5) Reproduce con un ping simple al registro

Golpea /v2/ con curl. Un registro correcto y accesible responde con 200 o 401. Un path equivocado, bloqueo por proxy o problema TLS aparece de inmediato.

Cómo funciona realmente la autenticación de registros Docker (las partes que importan)

docker login no “inicia sesión en Docker”. Negocia con un endpoint de registro específico y luego almacena una credencial (o token) para solicitudes HTTP posteriores de push/pull. Bajo el capó, es solo HTTP con algunas convenciones.

La API del registro y la sonda /v2/

La mayoría de registros implementan Docker Distribution (Registry HTTP API V2). Docker consulta https://REGISTRY/v2/ para ver si está vivo. La respuesta suele ser:

  • 200 OK: el registro permite acceso anónimo (raro para privado).
  • 401 Unauthorized con una cabecera WWW-Authenticate: el registro pide autenticación; Docker sigue el esquema indicado.
  • 404 o HTML: no estás hablando con un endpoint de registro (común detrás de load balancers o enrutamiento por path).

Flujos de token Bearer vs autenticación Basic

Muchos registros usan un flujo de token Bearer: el registro responde 401 con una cabecera WWW-Authenticate: Bearer ... apuntando a un servicio de tokens. Docker entonces solicita un token con scope para el repositorio y la operación (pull, push) y lo reenvía como Authorization: Bearer.

Otros usan Basic auth directamente (especialmente despliegues más antiguos o simples). Docker envía Authorization: Basic usando credenciales de tu config/helper.

Los scopes explican por qué “login exitoso” puede aún fallar

Un login exitoso solo significa “puedo autenticarme para obtener algún token/credencial almacenada”. Los fallos de push/pull suelen ser de autorización: el token existe pero no tiene el scope correcto. Ahí obtienes “requested access to the resource is denied” aunque “iniciaste sesión”.

Una cita que vale la pena recordar

Idea parafraseada (John Allspaw): La fiabilidad viene de mejorar el sistema, no de culpar a las personas después de que algo falla.

Hechos interesantes e historia breve (útil, no trivia)

  • Docker Hub no siempre fue “el predeterminado” en la mente de la gente. Los usuarios tempranos ejecutaban registries privados con frecuencia porque los controles de organización en Hub eran mínimos comparado con hoy.
  • Registry HTTP API V2 reemplazó a V1 en parte para soportar mejor seguridad e imágenes content-addressable; V1 está efectivamente muerto en el tooling moderno.
  • Los credential helpers se convirtieron en estándar porque almacenar credenciales base64 en JSON en claro era (y sigue siendo) vergonzoso en auditorías.
  • La integración con Keychain en macOS se volvió una expectativa de facto; Docker Desktop adoptó el almacenamiento seguro nativo temprano, lo que moldeó flujos de trabajo.
  • El almacenamiento de credenciales en Windows cambió con el tiempo a medida que Docker Desktop maduró; las configuraciones antiguas eran más hechas a mano y más frágiles.
  • La autenticación basada en tokens se expandió cuando los registros se integraron con SSO/IdPs; la “contraseña” a menudo se convirtió en “token de acceso personal” sin que la gente lo notara.
  • Content Trust (Notary v1) intentó hacer el firmado de imágenes mainstream; la adopción fue lenta, pero motivó a las organizaciones a preocuparse por la procedencia.
  • Los proxies empresariales e inspección TLS siguen siendo la causa número uno de “funciona en casa, falla en el trabajo” para logins de registro.

Chiste #1: La autenticación de Docker no es difícil. Solo tiene una preferencia fuerte por fallar durante las demos.

Almacenamiento de credenciales: helpers, keychains y config.json

Docker almacena credenciales mediante uno de tres patrones:

  1. Credential helper (preferido): keychain del SO, pass, secretservice, wincred, etc.
  2. Mapeo por registro de helpers (credHelpers): helper distinto para diferentes registros.
  3. Auth en línea (auths en config.json): base64 username:password. Funciona. También funciona para atacantes que puedan leer tu home.

Qué hace Docker realmente cuando “almacenas credenciales”

El CLI de Docker llama a un binario externo nombrado como docker-credential-osxkeychain o docker-credential-pass. Si ese binario falta, está roto o no puede acceder al keychain, el login falla aunque el registro acepte tus credenciales.

Los fallos de keychain parecen fallos del registro a menos que leas el error

Errores como error storing credentials suelen significar: Docker habló con el helper, y el helper se negó. Razones comunes incluyen:

  • Binary helper no instalado o no en PATH.
  • Keychain bloqueado (macOS), session bus ausente (Linux secretservice) o agente GPG no disponible (pass).
  • Permisos en CI headless donde no existe un keyring GUI.
  • config.json dañado por comas finales o ediciones manuales.

Guía con opinión

  • En portátiles: usa helpers del keychain del SO. No almacenes auth en texto plano en config.json.
  • En CI: evita keychains del SO. Usa --password-stdin con tokens efímeros, o configura un helper que funcione sin interfaz gráfica.
  • En servidores: elige repetibilidad sobre “seguridad por vibras”. Un keyring bloqueado a las 3 a.m. no es postura de seguridad; es un bug de fiabilidad.

Tareas prácticas: comandos, significados de salida y decisiones (12+)

Estas son tareas de runbook. Cada una incluye el comando, una salida representativa, lo que significa y qué decisión tomar a continuación.

Task 1: Confirmar Docker CLI y contexto

cr0x@server:~$ docker version
Client: Docker Engine - Community
 Version:           26.1.4
 API version:       1.45
 Go version:        go1.22.5
 Git commit:        5650f9b
 Built:             Wed Sep 18 10:21:00 2025
 OS/Arch:           linux/amd64

Server: Docker Engine - Community
 Engine:
  Version:          26.1.4
  API version:      1.45 (minimum version 1.24)
  Go version:       go1.22.5
  Git commit:       3d2c1f3
  Built:            Wed Sep 18 10:21:00 2025
  OS/Arch:          linux/amd64

Significado: Confirma que cliente/servidor están presentes, y que no estás usando un daemon remoto por arte del contexto.

Decisión: Si el cliente existe pero falta el servidor (común en problemas de Docker Desktop o shells remotos), arregla el daemon primero. Si las versiones son antiguas, espera incompatibilidades de auth/TLS.

Task 2: Verificar a qué registro te estás autenticando realmente

cr0x@server:~$ docker login
Authenticating with existing credentials...
Login Succeeded

Significado: Sin hostname, esto apunta a Docker Hub. Ese “éxito” puede ser irrelevante si necesitabas registry.corp.local.

Decisión: Siempre especifica el registro: docker login registry.corp.local. Si un compañero dice “login succeeded”, pregunta “¿a qué registro?”

Task 3: Inspeccionar la configuración de credenciales de Docker

cr0x@server:~$ cat ~/.docker/config.json
{
  "auths": {
    "registry.corp.local": {}
  },
  "credsStore": "secretservice"
}

Significado: Docker llamará a docker-credential-secretservice para guardar y recuperar creds.

Decisión: Si estás en CI o en un servidor sin session bus, cambia a un helper que funcione o usa un directorio de config temporal.

Task 4: Confirmar que el binario helper existe

cr0x@server:~$ command -v docker-credential-secretservice
/usr/bin/docker-credential-secretservice

Significado: El binario helper está instalado y en PATH.

Decisión: Si falta, instálalo o cambia credsStore. Si está presente, sigue a si puede acceder a su backend.

Task 5: Probar el helper directamente (diagnosticar acceso al keyring)

cr0x@server:~$ printf 'https://registry.corp.local\n' | docker-credential-secretservice get
error getting credentials - err: exit status 1, out: "Cannot autolaunch D-Bus without X11 $DISPLAY"

Significado: El helper depende de una sesión de escritorio (D-Bus + secret service). Un entorno headless no puede satisfacerlo.

Decisión: En CI/headless, no uses secretservice. Usa un helper distinto, o omite helpers usando un DOCKER_CONFIG aislado con auth inline (solo si puedes controlar permisos de archivo) y tokens efímeros.

Task 6: Ejecutar login con usuario y contraseña explícitos vía stdin (evitar historial)

cr0x@server:~$ printf '%s' "$REGISTRY_TOKEN" | docker login registry.corp.local -u ci-bot --password-stdin
Login Succeeded

Significado: La autenticación tuvo éxito y Docker intentó almacenar la credencial.

Decisión: Si esto falla con “error storing credentials”, el problema está en el helper/almacenamiento. Si falla con 401, el token/permiso es incorrecto.

Task 7: Activar debug del CLI de Docker para un intento de login

cr0x@server:~$ DOCKER_CLI_DEBUG=1 docker login registry.corp.local -u alice --password-stdin <<'EOF'
not-a-real-password
EOF
DEBU[0000] command: docker login registry.corp.local -u alice --password-stdin
DEBU[0000] credentials store: secretservice
Error response from daemon: login attempt to registry.corp.local failed with status: 401 Unauthorized

Significado: Confirma qué store de credenciales está activo y que el registro devolvió un 401 (no un error de red).

Decisión: Arregla credenciales/alcances/SSO/token; no pierdas tiempo en DNS/TLS todavía.

Task 8: Validar alcance y esquema de auth del registro con curl

cr0x@server:~$ curl -skI https://registry.corp.local/v2/
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Www-Authenticate: Bearer realm="https://auth.corp.local/token",service="registry.corp.local"
Docker-Distribution-Api-Version: registry/2.0

Significado: El registro es accesible, el handshake TLS tuvo éxito (porque usamos -k para ignorar confianza) y usa auth Bearer token.

Decisión: Si esto hace timeout, es red/proxy/DNS. Si devuelve HTML o 404, estás golpeando el endpoint equivocado/enrutamiento por path. Si falla sin -k, tienes un problema de confianza para arreglar.

Task 9: Volver a ejecutar curl sin -k para detectar fallos TLS reales

cr0x@server:~$ curl -sI https://registry.corp.local/v2/
curl: (60) SSL certificate problem: unable to get local issuer certificate

Significado: La tienda de confianza del SO no confía en la cadena de certificados del registro (o un proxy está intercambiando certs).

Decisión: Instala el certificado CA correcto en la confianza del daemon Docker y/o en la tienda del SO. No “arregles” esto haciendo el registro inseguro a menos que disfrutes las revisiones de incidentes.

Task 10: Revisar logs del daemon Docker para pistas de auth y TLS (systemd Linux)

cr0x@server:~$ sudo journalctl -u docker --since "10 minutes ago" --no-pager | tail -n 20
time="2026-01-02T09:12:44.112334512Z" level=error msg="Handler for POST /v1.45/auth returned error: login attempt to registry.corp.local failed with status: 401 Unauthorized"
time="2026-01-02T09:12:44.112612981Z" level=info msg="attempting next endpoint for registry.corp.local"

Significado: Confirma que el daemon vio el fallo de login y es un status de auth (no TCP/TLS).

Decisión: Si los logs del daemon mencionan x509 o handshake errors, ve directo a certificados. Si mencionan proxyconnect, ve a la configuración de proxy.

Task 11: Inspeccionar las variables proxy que Docker podría heredar

cr0x@server:~$ env | egrep -i 'http_proxy|https_proxy|no_proxy'
HTTPS_PROXY=http://proxy.corp.local:3128
NO_PROXY=localhost,127.0.0.1,.corp.local

Significado: El CLI de Docker usa tu entorno. El daemon Docker puede tener su propia config de proxy vía drop-ins de systemd.

Decisión: Si el registro no está en NO_PROXY, tu proxy podría estar interceptando TLS o bloqueando redirecciones del servicio de tokens. Añade el registro y los endpoints de auth a NO_PROXY para conexiones directas cuando corresponda.

Task 12: Comprobar la configuración de proxy del daemon (systemd drop-in)

cr0x@server:~$ systemctl show --property=Environment docker
Environment=HTTP_PROXY=http://proxy.corp.local:3128 HTTPS_PROXY=http://proxy.corp.local:3128 NO_PROXY=localhost,127.0.0.1

Significado: El daemon usa el proxy y NO_PROXY es demasiado pequeño.

Decisión: Amplía NO_PROXY para incluir tus hostnames de registro y dominios internos; reinicia Docker. Esto arregla la clase de bugs “pull funciona en shell pero falla en daemon”.

Task 13: Validar qué credenciales cree Docker que tiene (sin imprimir secretos)

cr0x@server:~$ docker system info 2>/dev/null | sed -n '1,40p'
Client:
 Version:    26.1.4
 Context:    default
 Debug Mode: false

Server:
 Containers: 12
  Running: 2
  Paused: 0
  Stopped: 10
 Server Version: 26.1.4
 Storage Driver: overlay2

Significado: No es directamente sobre credenciales, pero confirma qué daemon y driver de almacenamiento estás usando (útil cuando un “login” está ocurriendo en otro host por context).

Decisión: Si el context no es default, verifica que te estés autenticando contra el mismo daemon que hará el pull/push.

Task 14: Forzar a Docker a usar un directorio de config limpio (aislar de helpers rotos)

cr0x@server:~$ mkdir -p /tmp/docker-clean
cr0x@server:~$ DOCKER_CONFIG=/tmp/docker-clean docker login registry.corp.local -u alice --password-stdin <<'EOF'
correcthorsebatterystaple
EOF
WARNING! Your credentials are stored unencrypted in '/tmp/docker-clean/config.json'.
Login Succeeded

Significado: Esto evita tu configuración normal de helper y almacena auth inline. Docker te avisa porque debe hacerlo.

Decisión: Si esto funciona, tu problema original es la configuración del helper/keychain. Arregla eso correctamente; no vivas permanentemente en hacks en /tmp salvo que sea CI y controles el ciclo de vida.

Task 15: Verificar autorización del repositorio intentando una acción con scope

cr0x@server:~$ docker pull registry.corp.local/platform/base:latest
latest: Pulling from platform/base
Digest: sha256:7b2c6f25d4f7a2f2c1a0a1a3b6d5f1b9e20f6d2b3b0b7a1a2c3d4e5f6a7b8c9d
Status: Image is up to date for registry.corp.local/platform/base:latest

Significado: La auth funciona para pull en ese repo. Push aún puede fallar si te falta scope de push.

Decisión: Si el pull funciona pero el push falla, deja de hacer re-login. Arregla permisos en el registry/IdP/proyecto.

Task 16: Reproducir una denegación de push (clase diferente a “login failed”)

cr0x@server:~$ docker tag alpine:3.20 registry.corp.local/platform/base:test
cr0x@server:~$ docker push registry.corp.local/platform/base:test
The push refers to repository [registry.corp.local/platform/base]
denied: requested access to the resource is denied

Significado: Estás autenticado, pero no autorizado para hacer push a ese repo (o el nombre del repo es incorrecto, o el token no tiene scope para push).

Decisión: Revisa RBAC para ese repositorio/proyecto; valida que el token esté permitido para push, no solo lectura.

TLS, certificados CA y la familia “x509: unknown authority”

Los problemas TLS son la queja más común de “¿por qué falla solo dentro de la red de la empresa?”. Normalmente es una de tres cosas:

  • Tu registro usa una CA interna y tu máquina no la confía.
  • Un proxy/caja de inspección está terminando TLS y re-firmando con una CA corporativa que no has instalado (o que Docker no ve).
  • Estás golpeando el nombre equivocado (mismatch SNI) por DNS split-horizon o configuración del load balancer.

Dónde Docker lee la confianza (y dónde no)

En Linux, el daemon Docker mantiene su propio comportamiento de confianza además de la tienda del SO. Para registries personalizados, Docker soporta bundles CA por registro bajo /etc/docker/certs.d/REGISTRY_HOST[:PORT]/ca.crt.

En Docker Desktop (macOS/Windows), hay una capa extra: la VM Linux que ejecuta el daemon tiene su propia tienda de confianza. A veces importar certs en el keychain del SO no es suficiente a menos que Docker Desktop las sincronice.

No normalices “registros inseguros”

Sí, puedes configurar insecure-registries y seguir. No deberías, salvo que estés en un laboratorio desechable. Deshabilita la verificación TLS y conviertes un problema de login en un problema de cadena de suministro.

Chiste #2: Marcar un registro como “inseguro” es como quitar detectores de humo porque hacen ruido.

Proxies, cajas MITM y modos de fallo de red

Si estás detrás de un proxy corporativo, asume que forma parte del problema hasta que se demuestre lo contrario. Los flujos de auth de registros frecuentemente implican redirecciones entre registro y servicio de tokens. Los proxies son famosos por romper exactamente ese tipo de interacción multi-hop.

Síntomas clásicos de proxy

  • Los prompts de login se repiten porque el servicio de tokens está bloqueado y Docker sigue reintentando.
  • EOF a mitad de login porque un proxy cierra conexiones inactivas o bloquea transferencias chunked.
  • Funciona con curl pero no con Docker porque el daemon de Docker usa variables de proxy diferentes a tu shell.
  • “x509” solo en la oficina porque la intercepción TLS está habilitada en el camino del proxy.

NO_PROXY no es una sugerencia

Añade tu host de registro y el servicio de tokens a NO_PROXY cuando sean accesibles directamente. Incluye tanto el dominio del registro como cualquier sufijo interno usado por el realm de auth. Cuando los servicios de tokens están en un hostname distinto, olvidarlo en NO_PROXY es un fallo común.

DNS split-horizon y fallos por “endpoint equivocado”

Los registries privados a menudo tienen DNS interno y externo. Si tu portátil cambia de red (VPN on/off), el mismo hostname puede apuntar a diferentes load balancers con certificados y realms de auth distintos. Docker cachea credenciales por hostname, no por “entorno”. Así es como terminas autenticado en el “mismo nombre” equivocado.

Tokens, 2FA, SSO y la trampa de “la contraseña es correcta”

Los registros modernos frecuentemente están detrás de SSO, requieren 2FA o piden tokens de acceso personal (PAT) en lugar de contraseñas. Los humanos lo notan; la automatización a menudo no.

Cuando las contraseñas dejan de funcionar en silencio

Muchos sistemas permiten inicios de sesión en la UI con SSO pero requieren tokens para acceso por API. Docker usa la API del registro. Si tu organización cambió proveedores de autenticación, tu contraseña antigua puede seguir funcionando en el navegador y fallar en docker login. No es que Docker sea quisquilloso; estás usando el tipo de credencial equivocado.

El scope del token importa más que “token válido”

Un token puede ser válido pero no estar permitido para push a platform/base. Eso parece un problema de Docker hasta que te das cuenta de que el registro aplica permisos a nivel de repositorio. Arréglalo en RBAC, no regenerando tokens sin fin.

Flujos device code y CLIs de registro

Algunos registros ofrecen su propio CLI que realiza auth SSO por device-code y luego configura credenciales Docker. Eso puede estar bien en portátiles. En CI, evita flujos interactivos. Usa cuentas de servicio o tokens robot diseñados para automatización.

Tres mini-historias corporativas desde el campo

Incidente #1: La suposición equivocada (el “registro” no era el registro)

Una compañía mediana migró de un registro autohospedado a uno gestionado. Mantuvieron el mismo hostname, porque cambiar hostnames “rompe los flujos de trabajo de desarrolladores”, y todos asintieron como si fuera ley física.

Dos semanas después, la CI empezó a fallar. Los desarrolladores aún podían docker login con éxito. El error apareció en docker push como “requested access to the resource is denied”. La gente rotó contraseñas, regeneró tokens y probó distintas cuentas. Nada funcionó.

La causa raíz fue más aburrida: DNS split-horizon más VPN. Con VPN, registry.corp.local resolvía al nuevo registro gestionado. Sin VPN (o desde ciertos runners), resolvía a un load balancer interno antiguo que servía un endpoint de registro legacy con auth diferente. El login “sucedió” porque existían credenciales para ese hostname; el push falló porque el realm del servicio de tokens era diferente y la credencial almacenada no coincidía.

La solución no fue heroica. Crearon dos hostnames explícitos: uno interno y otro externo, y forzaron a la CI a usar uno consistentemente. También añadieron una pequeña comprobación que hace curl a /v2/ y valida que el realm en WWW-Authenticate coincida con lo esperado. Lección: reutilizar hostnames durante migraciones parece ordenado hasta que se convierte en una máquina de ambigüedad distribuida.

Incidente #2: Una optimización que salió mal (credential helper en todas partes)

Otra organización estandarizó estaciones de desarrollador usando un perfil de gestión de configuración. Alguien decidió que cada caja Linux debía usar secretservice para credenciales Docker, porque “es más seguro que texto plano”. Afirmación verdadera, contexto de despliegue equivocado.

Funcionó perfecto en escritorios. Luego aplicaron el mismo perfil a agentes de build que corren Linux headless. En el siguiente día de release, las compilaciones de imágenes empezaron a fallar en el paso de login con errores sobre D-Bus y displays faltantes. El registro estaba bien. Las credenciales estaban bien. El helper no lo estaba.

La respuesta al incidente fue caótica. La gente intentó reiniciar Docker, rotó contraseñas y culpó al proveedor del registro. Finalmente alguien corrió el helper directamente y obtuvo el mensaje clave: no podía autolaunch D-Bus en una sesión headless.

La solución fue dejar de tratar el “almacenamiento seguro” como una característica universal y empezar a tratarlo como una dependencia del sistema. Movieron la CI a usar --password-stdin con tokens robot de corta duración y establecieron DOCKER_CONFIG a un directorio de workspace destruido después del job. Mejoró seguridad, mejoró fiabilidad y nadie tuvo que SSH a runners para desbloquear keychains imaginarios.

Incidente #3: La práctica aburrida y correcta que salvó el día (verificación separada de auth)

Una gran empresa tenía una política: cada etapa de pipeline que hace push de imágenes debe primero verificar la accesibilidad del registro y el realm de auth antes de ejecutar el build. No era emocionante. Molestaba a la gente porque añadía segundos a los jobs.

En un trimestre, el equipo de networking rotó una configuración de proxy. La mayoría del HTTPS saliente empezó a atravesar una nueva capa de inspección TLS. Los desarrolladores vieron fallos intermitentes: algunos pulls funcionaban, algunos logins fallaban con errores x509, y el equipo de registro recibió paginaciones porque, por supuesto, lo hicieron.

Las pipelines que tenían la verificación preflight aburrida fallaron rápido con un error claro: curl a /v2/ devolvió una cadena de certificados firmada por un emisor distinto al esperado. Los jobs fallaron antes de construir nada. Eso cortó el radio de impacto drásticamente: no hubo artefactos parcialmente construidos, ni tiempo de runner desperdiciado, ni drama de “falla después de 20 minutos”.

Puesto que la salida preflight incluía el issuer del certificado y el realm de WWW-Authenticate, el equipo de registro pudo demostrar rápidamente que el registro no había cambiado. La solución fue distribuir la CA corporativa en la tienda de confianza de Docker en los runners afectados y asegurar que NO_PROXY evitara la inspección para los registries internos. Práctica aburrida. Práctica correcta. Salvó el día.

Errores comunes: síntoma → causa raíz → solución

1) “Login Succeeded” pero push dice denied

Síntoma: docker login tiene éxito; docker push devuelve denied: requested access to the resource is denied.

Causa raíz: Fallo de autorización (sin scope de push), path de repositorio incorrecto, o token con permisos solo de pull.

Solución: Verifica el nombre exacto del repo y el namespace del proyecto. Confirma que RBAC otorgue push. Usa un token para CI con los scopes correctos.

2) “error storing credentials” durante el login

Síntoma: El login falla después de introducir credenciales correctas; el error referencia almacenar credenciales.

Causa raíz: Helper de credenciales ausente/roto, keychain bloqueado, entorno headless sin secret service, o helper que no puede prompt.

Solución: Instala el helper, o elimina/ajusta credsStore para ese entorno. En CI, usa --password-stdin y un DOCKER_CONFIG limpio por job.

3) “x509: certificate signed by unknown authority”

Síntoma: Docker error menciona x509 o unknown authority.

Causa raíz: CA interna no confiada por el daemon; inspección TLS; mismatch de hostname/SNI.

Solución: Instala el certificado CA correcto en la confianza de Docker (/etc/docker/certs.d) y/o en la VM de Docker Desktop. Arregla DNS o SANs del certificado. Evita registros inseguros.

4) Prompts de contraseña repetidos o “unauthorized” tras introducir la contraseña correcta

Síntoma: Docker sigue solicitando credenciales o rechaza credenciales conocidas.

Causa raíz: El registro usa tokens/PATs; 2FA habilitado; SSO requiere un token, no una contraseña.

Solución: Usa PAT/token robot. Confirma el realm del servicio de tokens y los scopes requeridos. No uses flujos SSO interactivos en CI.

5) Funciona en portátil, falla en runner de CI

Síntoma: Los desarrolladores pueden iniciar sesión y push; CI no puede.

Causa raíz: CI usa proxy diferente, DNS distinto o no tiene acceso al helper del keychain; NO_PROXY distinto.

Solución: Haz explícita la ruta de red del runner. Configura proxy del daemon correctamente. Usa manejo de credenciales compatible con entornos headless.

6) Pull funciona, login falla (o viceversa)

Síntoma: Pull anónimo funciona pero login falla, o login tiene éxito pero pulls fallan en repos concretos.

Causa raíz: El registro permite acceso anónimo a algún contenido; o el servicio de tokens tiene problemas; o los permisos difieren por repo.

Solución: Prueba con curl -I /v2/ y un pull específico. Alinea la política de acceso; arregla el servicio de auth.

7) Docker Desktop: problemas de login tras actualizar el SO

Síntoma: Tras actualizaciones de macOS/Windows, los logins de Docker muestran errores relacionados con keychain/credential manager.

Causa raíz: Prompts del keychain bloqueados, corrupción del store de credenciales o mismatch del binario helper.

Solución: Reautoriza Docker Desktop para acceder al keychain, elimina entradas obsoletas o restablece credenciales en ajustes de Docker Desktop y vuelve a iniciar sesión.

8) “no basic auth credentials” durante pull

Síntoma: Pull falla con no basic auth credentials.

Causa raíz: No se encontraron credenciales para el hostname exacto del registro (incluyendo puerto), o la config está almacenada bajo una clave diferente.

Solución: Inicia sesión en el hostname:port exacto usado en las referencias de imagen. Revisa que las claves en ~/.docker/config.json coincidan.

Listas de verificación / plan paso a paso

Checklist A: Arreglar un portátil de desarrollador (macOS/Windows/Linux desktop)

  1. Confirma el hostname exacto del registro usado en las etiquetas de imagen (incluye puerto si hay).
  2. Ejecuta curl -I https://REGISTRY/v2/ para confirmar accesibilidad y ver el esquema de auth.
  3. Si TLS falla, instala la CA correcta y asegura que Docker Desktop/daemon la confíe.
  4. Inspecciona ~/.docker/config.json en busca de credsStore/credHelpers.
  5. Asegura que el helper exista y pueda acceder al keychain/credential manager del SO.
  6. Inicia sesión usando --password-stdin (incluso en portátiles) para evitar filtrado en el historial del shell.
  7. Prueba docker pull y docker push en un repo conocido para distinguir auth de permisos.

Checklist B: Arreglar runners de CI (Linux headless)

  1. Deja de usar credential helpers de escritorio a menos que sepas que funcionan headless.
  2. Usa un token de corta duración (robot/PAT) inyectado como secreto.
  3. Usa docker login ... --password-stdin con DOCKER_CONFIG apuntando a un directorio scoped al job.
  4. Confirma la configuración de proxy del daemon vía systemctl show --property=Environment docker.
  5. Ajusta NO_PROXY para incluir hostnames del registro y del servicio de tokens.
  6. Instala el certificado CA interno en la ruta de confianza del daemon para el registro.
  7. Añade un paso preflight: curl /v2/ y falla rápido si el realm o el issuer del certificado cambian.

Checklist C: Arreglar una integración de registry autohospedado

  1. Verifica que /v2/ devuelve el Docker-Distribution-Api-Version correcto y cabeceras de auth.
  2. Asegura que el realm/servicio de tokens sea alcanzable desde clientes y runners.
  3. Revisa que los SANs del certificado TLS incluyan el hostname que usan los clientes.
  4. Confirma que los permisos de repositorio se mapean correctamente a equipos/cuentas de servicio.
  5. Valida que el enrutamiento del load balancer no devuelva HTML/404 para /v2/.
  6. Prueba desde múltiples redes (VPN on/off) para detectar mismatches de split-horizon.

FAQ

¿Por qué docker login tiene éxito pero docker push falla?

El login prueba autenticación. Push requiere autorización para ese repositorio y el scope push. Arregla RBAC o los scopes del token, no el comando de login.

¿Cuál es la diferencia entre credsStore y credHelpers?

credsStore establece un helper por defecto para todos los registros. credHelpers mapea registries específicos a helpers específicos. Usa credHelpers cuando un registro necesita manejo especial.

¿Por qué obtengo “error storing credentials” en CI?

Probablemente configuraste un helper de keychain del SO que requiere una sesión GUI (secretservice, keychain). En CI, usa --password-stdin y un DOCKER_CONFIG por job, o un helper diseñado para uso headless.

¿Es seguro almacenar auth en ~/.docker/config.json?

Está en algo parecido a texto plano (base64) y debe tratarse como sensible. En sistemas compartidos es mala idea. En CI con workspaces efímeros y permisos restringidos puede ser aceptable como compromiso pragmático.

¿Qué significa realmente “no basic auth credentials”?

Docker no encontró credenciales para la clave exacta del registro que derivó de la referencia de la imagen. Una causa común es iniciar sesión en registry.corp.local pero hacer pull desde registry.corp.local:5000.

¿Debo usar insecure-registries para arreglar TLS?

No, no como solución rutinaria. Instala la CA correcta o arregla la cadena de certificados. Usa ajustes inseguros solo en entornos de prueba aislados donde aceptes el riesgo.

¿Por qué funciona con curl pero no con Docker?

curl usa tu entorno de usuario y la tienda de confianza del SO. El daemon Docker puede usar configuraciones de proxy diferentes y puede no confiar en las mismas CAs. Prueba ambos contextos: CLI y daemon.

¿Cómo manejo SSO/2FA con Docker login?

Usa un token de acceso personal o un token de cuenta de servicio diseñado para uso por API. Tu contraseña SSO interactiva a menudo no es válida para la API del registro.

¿Cuál es la forma más rápida de demostrar que es un problema de proxy?

Compara curl -I https://REGISTRY/v2/ con y sin variables de proxy, y comprueba si el realm en WWW-Authenticate o el issuer del certificado cambia. Si cambia, el proxy está en medio.

¿Necesito reiniciar Docker después de cambiar certificados?

En hosts Linux con daemon, a menudo sí (o al menos recargar). En Docker Desktop, a veces debes reiniciar Docker Desktop para que los cambios de confianza en la VM apliquen.

Conclusión: siguientes pasos que puedes ejecutar hoy

Si el inicio de sesión en el registro falla, no lo trates como una novela de misterio. Trátalo como un sistema: endpoints, confianza, esquema de auth, almacenamiento de credenciales y scopes.

  1. Ejecuta un curl -I https://REGISTRY/v2/ y lee el estado, las cabeceras y el comportamiento TLS.
  2. Inspecciona ~/.docker/config.json y verifica que el credential helper exista y funcione en tu entorno.
  3. Usa --password-stdin y un DOCKER_CONFIG limpio para aislar problemas con helpers de credenciales rápidamente.
  4. Separa “login succeeded” de “push permitido” probando pull/push en un repo conocido y arreglando RBAC cuando haga falta.
  5. Deja de parchear TLS con registros inseguros. Instala la CA correcta y hazlo determinista entre desarrollo y CI.

Una vez que hayas hecho esto unas cuantas veces, reconocerás los patrones. Y la próxima vez que alguien diga “funcionó ayer”, tendrás un playbook en lugar de un debate.

← Anterior
Los números de TDP de CPU en portátiles suelen ser cuentos de hadas
Siguiente →
Windows ME: cómo lanzar un sistema operativo que la gente recuerda como castigo

Deja un comentario