Ça arrive toujours au pire moment : vous êtes en réunion de déploiement, la build est verte et le push d’image échoue parce que docker login ne parvient pas à s’authentifier. Quelqu’un déclare « ça marchait hier » avec la confiance de quelqu’un qui n’a jamais rencontré de caches, de trousseaux ou de proxys d’entreprise.
Les échecs de connexion au registre ne sont rarement « juste des identifiants ». C’est généralement une petite guerre civile entre Docker, le magasin d’identifiants de votre OS, le schéma d’auth du registre, la chaîne de confiance TLS et n’importe quelle configuration réseau d’entreprise qui a décidé d’amuser ce trimestre. Gagnons cette guerre rapidement.
Playbook de diagnostic rapide (faire ceci en premier)
Ceci est l’ordre de triage qui fait gagner du temps. Il est optimisé pour remettre un runner CI ou un portable qui échoue à pousser des images sans tâtonner.
1) Confirmer ce qui a échoué : auth, TLS ou réseau
- Si vous voyez 401 Unauthorized ou denied: requested access to the resource is denied : authentification/scopes/permissions.
- Si vous voyez x509, certificate signed by unknown authority ou tls: handshake failure : chaîne de confiance / MITM / SNI.
- Si vous voyez timeout, no route, proxyconnect ou EOF : réseau/proxy/DNS.
- Si vous voyez error storing credentials, credentials store is not initialized ou exec: docker-credential-* : helper d’identifiants/trousseau.
2) Identifier quel endpoint de registre vous ciblez
Les gens tapent souvent le mauvais nom d’hôte (ou l’omettent), pointant silencieusement vers Docker Hub au lieu du registre privé. Votre terminal ne vous jugera pas ; il échouera simplement.
3) Inspecter la configuration effective des identifiants de Docker
Commencez par ~/.docker/config.json. S’il y a une entrée credsStore ou credHelpers, votre login échoue probablement dans le helper, pas sur le registre.
4) Activer le debug côté client pour une exécution
Utilisez DOCKER_CLI_DEBUG=1 ou les logs du démon Docker si vous êtes sur un hôte qui exécute le démon. La sortie de debug clarifie quel helper est appelé et quel endpoint est utilisé.
5) Reproduire avec un ping simple du registre
Interrogez /v2/ avec curl. Un registre correct et joignable répond par 200 ou 401. Un mauvais chemin, un blocage proxy ou un problème TLS apparaissent immédiatement.
Comment fonctionne réellement l’authentification du registre Docker (les parties importantes)
docker login ne « se connecte » pas à Docker de façon globale. Il négocie avec un endpoint de registre spécifique, puis stocke un identifiant (ou un jeton) pour les requêtes HTTP ultérieures de push/pull. Sous le capot, ce n’est que du HTTP avec quelques conventions.
L’API du registre et le probe /v2/
La plupart des registres implémentent Docker Distribution (Registry HTTP API V2). Docker teste https://REGISTRY/v2/ pour vérifier qu’il est vivant. La réponse est généralement :
- 200 OK : le registre autorise l’accès anonyme (rare pour du privé).
- 401 Unauthorized avec un en-tête
WWW-Authenticate: le registre demande une authentification ; Docker suit le schéma indiqué. - 404 ou HTML : vous ne parlez pas à un endpoint de registre (fréquent derrière des load balancers ou du routage par chemin).
Flux de token Bearer vs authentification Basic
Beaucoup de registres utilisent un flux de jeton Bearer : le registre répond 401 avec un WWW-Authenticate: Bearer ... pointant vers un service de token. Docker demande alors un jeton scoped au dépôt et à l’opération (pull, push), et le renvoie comme Authorization: Bearer.
D’autres utilisent directement l’authentification Basic (surtout les déploiements plus anciens ou plus simples). Docker envoie Authorization: Basic en utilisant les identifiants de votre config/helper.
Les scopes expliquent pourquoi « connexion réussie » peut encore échouer
Un login réussi signifie juste « je peux m’authentifier pour obtenir un jeton/identifiant stocké ». Les échecs de push/pull sont souvent des problèmes d’autorisation : le jeton existe mais n’a pas le scope correct. C’est alors que vous obtenez « requested access to the resource is denied » même si vous vous êtes « connecté ».
Une citation à garder
Idée paraphrasée (John Allspaw) : La fiabilité vient de l’amélioration du système, pas du blâme des individus après un échec.
Faits intéressants et courte histoire (utile, pas trivia)
- Docker Hub n’a pas toujours été « la valeur par défaut » dans l’esprit des gens. Les premiers utilisateurs de Docker exécutaient fréquemment des registres privés car les contrôles d’organisation de Hub étaient minimes comparés à aujourd’hui.
- L’API Registry HTTP V2 a remplacé la V1 en partie pour mieux supporter la sécurité et les images adressables par contenu ; V1 est pratiquement morte dans les outils modernes.
- Les credential helpers sont devenus la norme car stocker des identifiants encodés base64 dans un JSON en clair était (et reste) mal vu en audit.
- L’intégration du Keychain macOS est devenue une attente de facto ; Docker Desktop a adopté le stockage sécurisé natif de l’OS tôt, ce qui a modelé les workflows.
- Le stockage d’identifiants sous Windows a évolué au fil du temps avec Docker Desktop ; les configurations plus anciennes étaient plus bricolées et plus fragiles.
- L’authentification par jeton s’est étendue à mesure que les registres s’intégraient aux SSO/IdP ; le « mot de passe » est souvent devenu un « jeton d’accès personnel » sans que les gens le remarquent.
- Content Trust (Notary v1) a essayé de rendre la signature d’images plus courante ; l’adoption a pris du retard, mais cela a poussé les organisations à se soucier de la provenance.
- Les proxys d’entreprise et l’inspection TLS restent la cause numéro un des plaintes « ça marche à la maison, ça échoue au travail » pour les logins de registre.
Blague n°1 : L’auth Docker n’est pas difficile. Il préfère juste échouer pendant les démos.
Stockage des identifiants : helpers, trousseaux et config.json
Docker stocke les identifiants via l’un des trois schémas :
- Credential helper (préféré) : trousseau OS, pass, secretservice, wincred, etc.
- Mapping de helper par registre (
credHelpers) : helper différent pour différents registres. - Auth inline (
authsdansconfig.json) : base64username:password. Ça marche. Cela marche aussi pour des attaquants qui peuvent lire votre répertoire personnel.
Ce que Docker fait réellement quand vous « stockez des creds »
Le CLI Docker appelle un binaire externe nommé comme docker-credential-osxkeychain ou docker-credential-pass. Si ce binaire est absent, cassé ou ne peut pas accéder au trousseau, le login échoue même si le registre accepterait vos identifiants.
Les échecs de trousseau ressemblent à des échecs de registre à moins de lire l’erreur
Des erreurs comme error storing credentials signifient généralement : Docker a parlé au helper, et le helper a refusé. Raisons communes :
- Le binaire helper n’est pas installé ou n’est pas sur le
PATH. - Trousseau verrouillé (macOS), bus session manquant (Linux secretservice), ou agent GPG indisponible (pass).
- Permissions dans des CI sans interface graphique où aucun keyring n’existe.
config.jsoncorrompu à cause de virgules finales ou d’éditions manuelles.
Conseils opinés
- Sur les portables : utilisez les helpers OS keychain. Ne stockez pas d’auth en clair dans
config.json. - En CI : évitez les keychains OS. Utilisez
--password-stdinavec des jetons éphémères, ou configurez un helper compatible headless. - Sur les serveurs : privilégiez la répétabilité plutôt que la « sécurité par ambiance ». Un keyring verrouillé à 3 h du matin n’est pas une posture de sécurité ; c’est un bug de fiabilité.
Tâches pratiques : commandes, sens des sorties et décisions (12+)
Ce sont des tâches de niveau runbook. Chacune inclut la commande, une sortie représentative, ce que cela signifie, et la décision à prendre ensuite.
Task 1: Confirm Docker CLI and context
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
Signification : Confirme que le client/serveur sont présents, et que vous n’utilisez pas accidentellement un démon distant via un contexte magique.
Décision : Si le client existe mais que le serveur est manquant (fréquent avec Docker Desktop ou shells distants), corrigez d’abord le démon. Si les versions sont anciennes, attendez-vous à des incompatibilités auth/TLS.
Task 2: Verify which registry you’re actually logging into
cr0x@server:~$ docker login
Authenticating with existing credentials...
Login Succeeded
Signification : Sans nom d’hôte, ceci cible Docker Hub. Ce « succès » peut être sans rapport si vous aviez besoin de registry.corp.local.
Décision : Spécifiez toujours le registre : docker login registry.corp.local. Si un collègue dit « login succeeded », demandez « vers quel registre ? »
Task 3: Inspect Docker credential configuration
cr0x@server:~$ cat ~/.docker/config.json
{
"auths": {
"registry.corp.local": {}
},
"credsStore": "secretservice"
}
Signification : Docker appellera docker-credential-secretservice pour stocker et récupérer les creds.
Décision : Si vous êtes en CI ou sur un serveur headless sans bus de session, changez pour un helper fonctionnel ou utilisez un répertoire DOCKER_CONFIG isolé.
Task 4: Confirm the credential helper binary exists
cr0x@server:~$ command -v docker-credential-secretservice
/usr/bin/docker-credential-secretservice
Signification : Le binaire helper est installé et sur le PATH.
Décision : Si absent, installez-le ou changez credsStore. S’il est présent, vérifiez s’il peut accéder à son backend.
Task 5: Test the helper directly (diagnose keyring access)
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"
Signification : Le helper dépend d’une session de type desktop (D-Bus + secret service). Un environnement headless ne peut pas le satisfaire.
Décision : En CI/headless, n’utilisez pas secretservice. Utilisez un autre helper, ou évitez les helpers en utilisant un DOCKER_CONFIG isolé avec auth inline (seulement si vous contrôlez les permissions du fichier) et des jetons éphémères.
Task 6: Run login with explicit user and password via stdin (avoid shell history)
cr0x@server:~$ printf '%s' "$REGISTRY_TOKEN" | docker login registry.corp.local -u ci-bot --password-stdin
Login Succeeded
Signification : L’authentification a réussi et Docker a tenté de stocker l’identifiant.
Décision : Si cela échoue avec « error storing credentials », votre helper/stockage est en cause. Si cela échoue avec 401, le jeton/permissions sont incorrects.
Task 7: Turn on Docker CLI debug for one login attempt
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
Signification : Confirme quel store d’identifiants est actif et que le registre a renvoyé un 401 (ce n’est pas une erreur réseau).
Décision : Corrigez identifiants/scopes/SSO/token ; ne perdez pas de temps sur DNS/TLS pour l’instant.
Task 8: Validate registry reachability and auth scheme with 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
Signification : Le registre est joignable, la poignée TLS a réussi (car nous avons utilisé -k pour ignorer la confiance), et il utilise l’auth Bearer.
Décision : Si cela timeoute, c’est réseau/proxy/DNS. Si ça renvoie du HTML ou 404, vous touchez le mauvais endpoint/route. Si cela erreur sans -k, vous avez un problème de confiance à régler.
Task 9: Re-run curl without -k to catch real TLS failures
cr0x@server:~$ curl -sI https://registry.corp.local/v2/
curl: (60) SSL certificate problem: unable to get local issuer certificate
Signification : Le magasin de confiance de votre OS ne fait pas confiance à la chaîne de certificats du registre (ou un proxy échange les certificats).
Décision : Installez la bonne CA dans la confiance du démon Docker et/ou du magasin OS. Ne « corrigez » pas cela en rendant le registre insecure à moins d’aimer les revues d’incident.
Task 10: Check Docker daemon logs for auth and TLS hints (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"
Signification : Confirme que le démon a vu l’échec de login et que c’est un statut d’auth (pas TCP/TLS).
Décision : Si les logs du démon mentionnent x509 ou handshake errors, allez directement aux certificats. S’ils mentionnent proxyconnect, regardez la config du proxy.
Task 11: Inspect current proxy settings that Docker might inherit
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
Signification : Le CLI Docker utilise votre environnement. Le démon Docker peut avoir sa propre config proxy via des drop-ins systemd.
Décision : Si le registre n’est pas dans NO_PROXY, votre proxy peut intercepter TLS ou bloquer les redirections du service de token. Ajoutez le registre et les endpoints d’auth à NO_PROXY pour des connexions directes si approprié.
Task 12: Check daemon proxy config (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
Signification : Le démon lui-même utilise le proxy, et NO_PROXY est trop limité.
Décision : Étendez NO_PROXY pour inclure vos noms d’hôte de registre et domaines internes ; redémarrez Docker. Cela corrige la classe de bugs « pull marche dans le shell mais échoue dans le démon ».
Task 13: Validate what credentials Docker thinks it has (without printing secrets)
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
Signification : Pas directement à propos des identifiants, mais confirme quel démon et quel driver de stockage vous utilisez (utile quand un « login » se fait sur un hôte différent via le contexte).
Décision : Si le contexte n’est pas default, vérifiez que vous vous authentifiez contre le même démon qui effectuera le pull/push.
Task 14: Force Docker to use a clean config directory (isolate from broken helpers)
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
Signification : Cela contourne votre configuration helper normale et stocke l’auth inline. Docker vous avertit parce qu’il le faut.
Décision : Si cela fonctionne, votre problème original est la configuration du helper/trousseau. Corrigez cela proprement ; ne vivez pas éternellement dans des hacks /tmp sauf si c’est en CI et que vous contrôlez le cycle de vie.
Task 15: Verify repository authorization by attempting a scoped action
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
Signification : L’auth fonctionne pour le pull de ce repo. Le push peut encore échouer si vous n’avez pas le scope push.
Décision : Si le pull réussit mais que le push échoue, arrêtez de relancer des logins. Corrigez les permissions dans le registre/IdP/projet.
Task 16: Reproduce a push denial (different class than “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
Signification : Vous êtes authentifié, mais pas autorisé à pousser vers ce repo (ou le nom du repo est erroné, ou le jeton n’a pas le scope).
Décision : Vérifiez le RBAC pour ce repository/projet ; validez que le jeton a le droit de push, pas seulement de read.
TLS, certificats CA et la famille « x509: unknown authority »
Les problèmes TLS sont la plainte la plus courante du type « pourquoi ça échoue seulement dans le réseau de l’entreprise ? ». Généralement c’est l’une des trois choses :
- Votre registre utilise une CA interne et votre machine ne lui fait pas confiance.
- Un proxy/boîte d’inspection termine le TLS et re-signe avec une CA d’entreprise que vous n’avez pas installée (ou que Docker ne voit pas).
- Vous atteignez le mauvais nom (mismatch SNI) à cause d’un DNS split-horizon ou d’une configuration de load balancer.
Où Docker lit la confiance (et où il ne la lit pas)
Sur Linux, le démon Docker conserve son propre comportement de confiance en plus du magasin OS. Pour les registres personnalisés, Docker supporte les bundles CA par registre sous /etc/docker/certs.d/REGISTRY_HOST[:PORT]/ca.crt.
Sur Docker Desktop (macOS/Windows), il y a une couche supplémentaire : la VM Linux qui exécute le démon a son propre magasin de confiance. Parfois, l’import dans le trousseau OS ne suffit pas à moins que Docker Desktop ne les synchronise.
NORMALISEZ PAS « registres non sécurisés »
Oui, vous pouvez configurer insecure-registries et passer à autre chose. Non, vous ne devriez pas le faire sauf si c’est pour un laboratoire jetable. Cela désactive la vérification TLS et transforme un problème de login en problème de chaîne d’approvisionnement.
Blague n°2 : Marquer un registre « insecure » est comme retirer les détecteurs de fumée parce qu’ils font du bruit.
Proxys, boîtes MITM et modes d’échec réseau
Si vous êtes derrière un proxy d’entreprise, supposez qu’il fait partie du problème jusqu’à preuve du contraire. Les flux d’auth du registre impliquent fréquemment des redirections entre le registre et le service de token. Les proxys sont célèbres pour casser exactement ce type d’interaction multi-hop.
Symptômes classiques de proxy
- Les demandes de login se répètent parce que le service de token est bloqué et Docker réessaie sans fin.
- EOF en plein login parce qu’un proxy ferme des connexions inactives ou bloque le transfert chunked.
- Ça marche avec curl mais pas avec Docker parce que le démon Docker utilise des variables proxy différentes de votre shell.
- « x509 » seulement au travail parce que l’interception TLS est activée sur le chemin proxy.
NO_PROXY n’est pas une suggestion
Mettez votre hôte de registre et le service de token dans NO_PROXY quand ils sont joignables directement. Incluez à la fois le domaine du registre et tous les suffixes internes utilisés par le realm d’auth. Quand les services de token sont sur un nom d’hôte différent, l’oubli de l’ajouter à NO_PROXY est une cause fréquente d’échec.
DNS split-horizon et échecs « mauvais endpoint »
Les registres privés ont souvent un DNS interne et externe. Si votre portable change de réseau (VPN on/off), le même nom d’hôte peut pointer vers des load balancers différents avec des certificats TLS et des realms d’auth différents. Docker met en cache les identifiants par nom d’hôte, pas par « environnement ». C’est ainsi que vous vous retrouvez connecté au mauvais registre portant le même nom.
Jetons, 2FA, SSO et le piège « le mot de passe est correct »
Les registres modernes sont souvent derrière un SSO, imposent la 2FA, ou exigent des jetons d’accès personnels (PAT) au lieu de mots de passe. Les humains le remarquent ; l’automatisation souvent pas.
Quand les mots de passe cessent de fonctionner en silence
Beaucoup de systèmes autorisent les connexions UI via SSO mais exigent des jetons pour l’accès API. Docker utilise l’API du registre. Si votre organisation a changé de fournisseur d’authentification, votre ancien mot de passe peut encore fonctionner dans un navigateur et échouer dans docker login. Ce n’est pas que Docker soit pointilleux ; c’est que vous utilisez le mauvais type d’identifiant.
Le scope du jeton compte plus que « jeton valide »
Un jeton peut être valide mais ne pas être autorisé à pousser vers platform/base. Ça ressemble à un problème Docker jusqu’à ce que vous réalisiez que le registre applique des permissions au niveau du dépôt. Corrigez le RBAC, n’appelez pas sans fin la génération de jetons.
Flows device code et CLIs de registre
Certaines plateformes fournissent leur propre CLI qui réalise une authentification SSO via device-code puis configure les identifiants Docker. C’est acceptable sur des portables. En CI, évitez les flows interactifs. Utilisez des comptes de service ou des jetons robot conçus pour l’automatisation.
Trois mini-récits d’entreprise depuis le terrain
Incident #1 : La mauvaise hypothèse (le registre n’était pas le registre)
Une entreprise de taille moyenne a migré d’un registre self-hosted vers un service managé. Ils ont gardé le même nom d’hôte, parce que changer les hostnames « casse les workflows des développeurs », et tout le monde a hoché la tête comme si c’était une loi de la physique.
Deux semaines plus tard, la CI a commencé à échouer. Les développeurs pouvaient toujours faire docker login avec succès. L’erreur apparaissait sur docker push comme « requested access to the resource is denied ». Les gens ont fait tourner les mots de passe, régénéré des jetons et essayé différents comptes. Rien n’a tenu.
La cause racine était plus ennuyeuse : DNS split-horizon plus VPN. Sur le VPN, registry.corp.local résolvait vers le nouveau registre managé. Hors VPN (ou depuis certains runners), il résolvait vers un ancien load balancer interne qui servait un endpoint de registre legacy avec une auth différente. Le login « réussissait » parce que des identifiants existaient pour le nom d’hôte ; le push échouait parce que le realm du service de token était différent et l’identifiant stocké ne correspondait pas.
La correction n’a pas été héroïque. Ils ont créé deux hostnames explicites : un interne, un externe, et forcé la CI à en utiliser un de façon cohérente. Ils ont aussi ajouté une petite vérification qui curl /v2/ et valide que le realm WWW-Authenticate correspond aux attentes. Leçon : réutiliser des hostnames pendant des migrations semble propre jusqu’à ce que ça devienne une machine d’ambiguïté distribuée.
Incident #2 : Une optimisation qui s’est retournée contre eux (credential helper partout)
Une autre organisation a standardisé les postes développeurs en appliquant un profil de configuration. Quelqu’un a décidé que toutes les machines Linux devaient utiliser secretservice pour les identifiants Docker, parce que « c’est plus sûr que le plaintext ». Affirmation vraie, mauvais contexte de déploiement.
Ça fonctionnait parfaitement sur les desktops. Puis ils ont appliqué le même profil aux agents de build headless Linux. Le jour de la release suivante, les builds d’images ont commencé à échouer à l’étape de login avec des erreurs sur D-Bus et affichage manquant. Le registre était OK. Les identifiants étaient OK. Le helper ne l’était pas.
La réponse à l’incident a été prévisible et chaotique. Les gens ont essayé de redémarrer Docker, fait tourner les mots de passe et accusé le fournisseur du registre. Finalement, quelqu’un a lancé le helper directement et obtenu le message clé : il ne pouvait pas autolaunch D-Bus dans une session headless.
La correction a été d’arrêter de traiter le « stockage sécurisé » comme une fonction universelle et de commencer à le traiter comme une dépendance système. Ils ont déplacé la CI pour utiliser --password-stdin avec des jetons robots short-lived et ont défini DOCKER_CONFIG sur un répertoire workspace détruit après le job. La sécurité s’est améliorée, la fiabilité aussi, et personne n’a eu besoin de SSHer dans des runners pour déverrouiller des trousseaux imaginaires.
Incident #3 : La bonne pratique ennuyeuse qui a sauvé la mise (vérification d’auth séparée)
Une grande entreprise avait une politique : chaque étape de pipeline qui pousse des images doit d’abord vérifier la joignabilité du registre et le realm d’auth avant de lancer la build. Ce n’était pas excitant. Ça agaçait les gens parce que ça ajoutait des secondes aux jobs.
Un trimestre, l’équipe réseau a tourné une configuration de proxy. La plupart des HTTPS sortants ont commencé à traverser une nouvelle couche d’inspection TLS. Les développeurs ont vu des échecs intermittents : certains pulls marchaient, certains logins échouaient avec des erreurs x509, et bien sûr l’équipe registre a été alertée.
Les pipelines qui avaient la vérification préliminaire ennuyeuse ont échoué rapidement avec une erreur claire : curl sur /v2/ a renvoyé une chaîne de certificats signée par un émetteur différent de celui attendu. Les jobs échouants se sont arrêtés avant de construire quoi que ce soit. Ça a considérablement réduit le blast radius : pas d’artéfacts à moitié construits, pas de temps de runner gaspillé, pas de drame « ça échoue après 20 minutes ».
Parce que la sortie préflight incluait l’émetteur du certificat et le realm WWW-Authenticate, l’équipe registre a pu prouver rapidement que le registre n’avait pas changé. La correction a été de distribuer la CA d’entreprise dans la confiance du démon Docker sur les runners affectés et de s’assurer que NO_PROXY contournait l’inspection pour les registres internes. Pratique ennuyeuse. Pratique correcte. A sauvé la mise.
Erreurs courantes : symptôme → cause racine → correction
1) « Login Succeeded » mais le push indique denied
Symptôme : docker login réussit ; docker push renvoie denied: requested access to the resource is denied.
Cause racine : Échec d’autorisation (pas de scope push), mauvais chemin de repository, ou jeton en lecture seule.
Correction : Vérifiez le nom exact du repo et l’espace de projet. Confirmez que le RBAC accorde le push. Utilisez un jeton CI destiné avec les scopes corrects.
2) « error storing credentials » pendant le login
Symptôme : Le login échoue après saisie de bons identifiants ; l’erreur référence le stockage des identifiants.
Cause racine : Helper d’identifiants manquant/cassé, trousseau verrouillé, environnement headless sans secret service, ou helper incapable de demander une saisie.
Correction : Installez le helper, ou supprimez/ajustez credsStore pour cet environnement. En CI, utilisez --password-stdin et un DOCKER_CONFIG propre et job-scoped.
3) « x509: certificate signed by unknown authority »
Symptôme : Docker affiche des erreurs mentionnant x509 ou unknown authority.
Cause racine : CA interne non approuvée par le démon ; interception TLS ; mauvais hostname/SNI mismatch.
Correction : Installez la bonne CA dans la confiance Docker (/etc/docker/certs.d) et/ou dans la VM Docker Desktop. Corrigez le DNS ou les SANs du certificat. Évitez les registres insecure.
4) Prises de mot de passe répétées ou « unauthorized » après un bon mot de passe
Symptôme : Docker redemande sans cesse des identifiants, ou rejette des identifiants connus bons.
Cause racine : Le registre utilise des jetons/PATs ; 2FA activée ; SSO exige un jeton, pas un mot de passe.
Correction : Utilisez un PAT/jeton robot. Confirmez le realm du service de token et les scopes requis. Évitez les flux SSO interactifs en CI.
5) Ça marche sur le portable, échoue sur le runner CI
Symptôme : Les développeurs peuvent se connecter et pousser ; la CI ne peut pas.
Cause racine : Le CI utilise un proxy, DNS différent, ou n’a pas accès au helper keychain ; NO_PROXY différent.
Correction : Rendre explicite le chemin réseau du runner. Configurer le proxy du démon correctement. Utiliser un traitement d’identifiants compatible headless.
6) Pull fonctionne, login échoue (ou vice versa)
Symptôme : Les pulls anonymes réussissent mais le login échoue, ou le login réussit mais les pulls échouent pour certains repos.
Cause racine : Le registre autorise l’accès anonyme à certains contenus ; ou le service de token a des problèmes ; ou les permissions varient par repo.
Correction : Testez avec curl -I /v2/ et un pull spécifique. Alignez la politique d’accès ; corrigez le service d’auth.
7) Docker Desktop : problèmes de login après mise à jour OS
Symptôme : Après une mise à jour macOS/Windows, les erreurs de login mentionnent keychain/credential manager.
Cause racine : Prompts du trousseau bloqués, corruption du store d’identifiants, ou mismatch du binaire helper.
Correction : Réautorisez Docker Desktop à accéder au trousseau, supprimez les entrées obsolètes, ou réinitialisez les identifiants dans les paramètres Docker Desktop et reconnectez-vous.
8) « no basic auth credentials » pendant un pull
Symptôme : Le pull échoue avec no basic auth credentials.
Cause racine : Identifiants non trouvés pour le nom d’hôte exact du registre (y compris le port), ou config stockée sous une clé différente.
Correction : Connectez-vous au nom d’hôte:port exact utilisé dans les références d’image. Vérifiez que les clés dans ~/.docker/config.json correspondent.
Listes de vérification / plan étape par étape
Checklist A: Fix a developer laptop (macOS/Windows/Linux desktop)
- Confirmez le nom d’hôte exact du registre utilisé dans les tags d’image (inclure le port le cas échéant).
- Exécutez
curl -I https://REGISTRY/v2/pour confirmer la joignabilité et voir le schéma d’auth. - Si TLS échoue, installez la bonne CA et assurez-vous que Docker Desktop/démon lui fait confiance.
- Inspectez
~/.docker/config.jsonpourcredsStore/credHelpers. - Assurez-vous que le helper existe et peut accéder au gestionnaire d’identifiants de l’OS.
- Connectez-vous en utilisant
--password-stdin(même sur portable) pour éviter les fuites d’historique de shell. - Testez
docker pulletdocker pushsur un repo connu bon pour distinguer auth vs permissions.
Checklist B: Fix CI runners (headless Linux)
- Arrêtez d’utiliser des credential helpers de bureau à moins de savoir qu’ils fonctionnent headless.
- Utilisez un jeton court-lived (robot/PAT) injecté comme secret.
- Utilisez
docker login ... --password-stdinavecDOCKER_CONFIGdéfini sur un répertoire job-scoped. - Confirmez la configuration proxy du démon via
systemctl show --property=Environment docker. - Définissez
NO_PROXYpour inclure les hostnames du registre et du service de token. - Installez le certificat CA interne dans le chemin de confiance du démon pour le registre.
- Ajoutez une étape preflight : curl
/v2/et échouez vite si le realm ou l’émetteur du certificat est incorrect.
Checklist C: Fix a self-hosted registry integration
- Vérifiez que
/v2/renvoie le bonDocker-Distribution-Api-Versionet les en-têtes d’auth. - Assurez-vous que le realm/service de token est joignable depuis les clients et runners.
- Vérifiez que les SANs du certificat TLS incluent le nom d’hôte que les clients utilisent.
- Confirmez que les permissions des repositories sont correctement mappées aux équipes/comptes de service.
- Validez que le routage du load balancer ne renvoie pas de HTML/404 pour
/v2/. - Testez depuis plusieurs réseaux (VPN on/off) pour détecter des mismatches split-horizon.
FAQ
Pourquoi docker login réussit mais docker push échoue ?
Le login prouve l’authentification. Le push exige l’autorisation pour ce repository et le scope push. Corrigez le RBAC ou les scopes du jeton, pas la commande de login.
Quelle est la différence entre credsStore et credHelpers ?
credsStore définit un helper par défaut pour tous les registres. credHelpers mappe des registres spécifiques à des helpers spécifiques. Utilisez credHelpers lorsqu’un registre nécessite un traitement spécial.
Pourquoi ai-je « error storing credentials » en CI ?
Vous avez probablement configuré un helper keychain OS qui nécessite une session GUI (secretservice, keychain). En CI, utilisez --password-stdin et un DOCKER_CONFIG scope-job, ou un helper conçu pour l’utilisation headless.
Est-il sûr de stocker l’auth dans ~/.docker/config.json ?
C’est en clair-ish (base64) et doit être traité comme sensible. Sur des systèmes partagés, c’est une mauvaise idée. En CI avec des workspaces éphémères et des permissions verrouillées, cela peut être acceptable comme compromis pragmatique.
Que signifie réellement « no basic auth credentials » ?
Docker n’a pas trouvé d’identifiants pour la clé exacte de registre qu’il a dérivée de la référence d’image. Une cause fréquente est de se connecter à registry.corp.local mais de tirer depuis registry.corp.local:5000.
Dois-je utiliser insecure-registries pour corriger TLS ?
Non, pas comme correction de routine. Installez la CA appropriée ou corrigez la chaîne de certificats. N’utilisez les registres insecure que pour des environnements de test isolés où vous acceptez le risque.
Pourquoi ça marche avec curl mais pas avec Docker ?
curl utilise votre environnement utilisateur et le magasin de confiance OS. Le démon Docker peut utiliser des paramètres proxy différents et ne pas faire confiance aux mêmes CAs. Testez les contextes CLI et démon.
Comment gérer SSO/2FA avec Docker login ?
Utilisez un jeton d’accès personnel ou un jeton de compte de service conçu pour l’usage API. Votre mot de passe SSO interactif n’est souvent pas valide pour l’API du registre.
Quelle est la manière la plus rapide de prouver que c’est un problème de proxy ?
Comparez curl -I https://REGISTRY/v2/ avec et sans variables proxy, et vérifiez si le realm WWW-Authenticate ou l’émetteur du certificat change. Si oui, le proxy est au milieu.
Faut-il redémarrer Docker après avoir changé les certificats ?
Sur des hôtes daemon Linux, souvent oui (ou au moins reload). Sur Docker Desktop, parfois vous devez redémarrer Docker Desktop pour que les changements de confiance dans la VM soient pris en compte.
Conclusion : prochaines étapes que vous pouvez exécuter aujourd’hui
Si la connexion au registre échoue, ne traitez pas cela comme un roman à mystère. Traitez-le comme un système : endpoints, confiance, schéma d’auth, stockage des identifiants et scopes.
- Exécutez un
curl -I https://REGISTRY/v2/et lisez le statut, les en-têtes et le comportement TLS. - Inspectez
~/.docker/config.jsonet vérifiez que le helper d’identifiants existe et fonctionne dans votre environnement. - Utilisez
--password-stdinet unDOCKER_CONFIGpropre pour isoler rapidement les problèmes de helper d’identifiants. - Séparez « login succeeded » de « push autorisé » en testant pull/push sur un repo connu et corrigez le RBAC si nécessaire.
- Arrêtez de masquer les problèmes TLS avec des registres insecure. Installez la bonne CA et rendez cela déterministe entre dev et CI.
Une fois que vous l’aurez fait quelques fois, vous reconnaîtrez les schémas. Et la prochaine fois que quelqu’un dira « ça marchait hier », vous aurez un playbook au lieu d’un débat.