Aller au contenu
Conteneurs & Orchestration medium

Docker Hub : publier vos images (push/pull), PAT et CI/CD GitHub Actions

28 min de lecture

Vous voulez publier vos images Docker pour les déployer sur vos serveurs ? Docker Hub est la registry publique la plus utilisée au monde. Ce guide vous montre comment créer un compte, configurer l'authentification par token (PAT), et automatiser vos builds avec GitHub Actions, en tenant compte des quotas de téléchargement actuellement en vigueur.

À la fin de ce guide, vous serez capable de gérer l'ensemble du cycle de vie de vos images Docker sur Docker Hub, de la création du compte jusqu'à l'automatisation complète en CI/CD.

  • Créer un compte et comprendre les différents plans (gratuit vs payant)
  • Configurer un Personal Access Token (PAT), obligatoire avec la double authentification ou le SSO
  • Publier une image avec docker push (et la récupérer avec docker pull)
  • Automatiser vos builds avec GitHub Actions
  • Choisir les bonnes images parmi les 3 types de contenu de confiance
  • Éviter le rate limiting et les erreurs 429 Too Many Requests

Docker Hub est la registry publique officielle de Docker : un serveur qui reçoit vos images par docker push, les stocke sous forme de couches adressées par empreinte, et les redistribue à toute machine qui exécute un docker pull. C'est la destination utilisée par défaut par la CLI Docker quand aucun autre registre n'est précisé dans le nom de l'image.

Ce que cela signifie concrètement pour vous :

  • Bibliothèque de plusieurs millions d'images, du système d'exploitation minimal au serveur de base de données prêt à l'emploi
  • Images officielles maintenues conjointement par Docker et les projets amont (nginx, postgres, redis, ubuntu)
  • Registre par défaut de la CLI : docker pull nginx récupère en réalité docker.io/library/nginx
  • Quotas de téléchargement appliqués aux comptes gratuits, le point qui surprend le plus en CI

Docker Hub ne contient pas que des images de qualité égale. Il existe 3 niveaux de confiance officiels :

Les 3 types de contenu de confiance sur Docker Hub : Official Images,
Verified Publishers, Sponsored OSS

TypeBadgeQui maintient ?Exemples
Official Images🛡️ VertDocker + communauténginx, postgres, redis, ubuntu
Verified Publishers✓ BleuÉditeurs commerciaux vérifiésbitnami, microsoft, canonical
Sponsored OSS🌐 VioletProjets open source sponsorisésgrafana, traefik, prometheus

Le choix qui compte dans ce parcours est le username : il devient votre namespace et préfixe définitivement toutes vos images sous la forme username/image. Or un Docker ID ne se renomme pas ; la documentation officielle précise qu'il faut créer un nouveau compte, et qu'un identifiant désactivé n'est jamais réattribué. Prenez donc un nom stable, en minuscules, que vous assumerez dans vos fichiers Compose et vos manifestes Kubernetes.

  1. Accéder au site

    Rendez-vous sur hub.docker.com et cliquez sur Sign Up en haut à droite.

  2. Remplir le formulaire

    • Username : ce sera votre namespace (ex: monuser/monimage)
    • Email : pour la confirmation et les notifications
    • Password : minimum 9 caractères, avec majuscules et chiffres
  3. Confirmer votre email

    Docker envoie un email de vérification. Cliquez sur le lien dans les 24h.

  4. Se connecter

    Retournez sur Docker Hub et connectez-vous. Vous accédez à votre tableau de bord.

Docker Hub propose plusieurs formules adaptées à différents besoins. Le choix dépend principalement de trois facteurs : le nombre de dépôts privés dont vous avez besoin, votre volume de pulls, et si vous travaillez en équipe.

FonctionnalitéPersonal (gratuit)Pro ($9/user/mois annuel, $11 mensuel)Team ($15/user/mois annuel, $16 mensuel)
Dépôts publicsIllimitésIllimitésIllimités
Dépôts privés1IllimitésIllimités
Pulls par 6 heures (authentifié)200IllimitéIllimité
Pulls par 6 heures (anonyme)100 par IPv4 ou par /64 IPv6--
Docker Scout1 dépôt2 dépôtsIllimité
Équipes & RBAC

Un Personal Access Token est une chaîne secrète que vous présentez à la place de votre mot de passe lors du docker login. Deux situations le rendent obligatoire, pas seulement recommandé : quand la double authentification est activée sur votre compte, et quand votre organisation impose le SSO. Dans tous les autres cas, il reste facultatif, mais c'est le seul mécanisme qui vous laisse révoquer un accès de CI sans changer vos identifiants personnels.

Pourquoi utiliser un PAT plutôt qu'un mot de passe ?

Section intitulée « Pourquoi utiliser un PAT plutôt qu'un mot de passe ? »

Les Personal Access Tokens (PAT) offrent une sécurité bien supérieure aux mots de passe traditionnels. Imaginez que vous utilisez le même mot de passe pour votre laptop, votre CI/CD et un script de déploiement : si l'un est compromis, tout est compromis. Avec les PAT, vous créez un token par usage, chacun avec des permissions limitées et une date d'expiration.

CritèreMot de passePersonal Access Token
RévocationImpossible sans changer partoutRévocable individuellement
PermissionsAccès total au compteRead, Write, ou Delete séparés
ExpirationJamaisConfigurable (30, 60, 90 jours...)
AuditAucune traçabilitéLogs d'utilisation
2FA compatible❌ Bloqué si 2FA activé✅ Fonctionne toujours

Deux points de cette procédure sont irréversibles et méritent votre attention avant de cliquer. Le token n'est affiché qu'une fois : si vous fermez la page sans le copier, il faut en générer un autre. Et la date d'expiration ne se modifie pas après coup, la documentation Docker est explicite sur ce point : changer l'échéance impose de créer un nouveau token. Choisissez donc la durée en fonction de votre politique de rotation, pas au hasard.

  1. Accéder aux paramètres du compte

    Connectez-vous sur app.docker.com → Cliquez sur votre avatar → Account settingsPersonal access tokens.

  2. Générer un nouveau token

    Cliquez sur Generate new token et configurez :

    • Description : nom explicite (ex: github-actions-prod, laptop-dev)
    • Expiration : 90 jours recommandé pour la CI/CD
    • Permissions :
      • Read : pour docker pull uniquement
      • Read & Write : pour docker push et docker pull
      • Read, Write & Delete : accès complet (éviter en CI/CD)
  3. Copier le token immédiatement

    Le token n'est affiché qu'une seule fois. Copiez-le dans un gestionnaire de secrets (Vault, 1Password, GitHub Secrets...).

  4. Tester la connexion

    Fenêtre de terminal
    # Connexion avec le PAT (remplacez YOUR_TOKEN)
    echo "YOUR_TOKEN" | docker login -u votre-username --password-stdin
    # Résultat attendu
    Login Succeeded

Vous avez créé une image localement et voulez la partager ? Voici le workflow complet.

Cette première étape se déroule entièrement en local, aucune connexion à Docker Hub n'est encore nécessaire. Le point à vérifier dans la sortie de docker images est la taille : au-delà de quelques centaines de mégaoctets pour une application applicative simple, votre Dockerfile embarque probablement des outils de compilation qu'un build multi-étapes aurait écartés. Autant le corriger avant de pousser, chaque couche publiée reste dans l'historique du dépôt.

Fenêtre de terminal
# 1. Construire l'image avec un tag local
docker build -t monapp:v1.0.0 .
# 2. Vérifier que l'image existe
docker images | grep monapp
# monapp v1.0.0 abc123def456 2 minutes ago 150MB

Le format du tag Docker Hub est username/repository:tag, et le préfixe n'est pas décoratif : c'est lui qui indique au démon vers quel dépôt distant pousser. Une image nommée monapp:v1.0.0 sans préfixe sera refusée, Docker tentant de l'envoyer vers la bibliothèque officielle library/. docker tag ne duplique rien : il ajoute simplement un nom supplémentaire au même identifiant d'image, l'opération est instantanée et ne consomme pas d'espace disque.

Fenêtre de terminal
# 3. Taguer l'image pour Docker Hub
docker tag monapp:v1.0.0 monuser/monapp:v1.0.0
# Optionnel : ajouter un tag "latest"
docker tag monapp:v1.0.0 monuser/monapp:latest

Le --password-stdin n'est pas une coquetterie : passer le token en argument avec -p le laisse dans l'historique du shell et dans la table des processus, visible par tout utilisateur de la machine. La sortie du push mérite une lecture attentive, chaque ligne correspond à une couche et affiche Pushed, ou Layer already exists quand la couche est déjà présente sur le registre. La dernière ligne donne le digest de l'image, l'empreinte que vous réutiliserez pour un déploiement reproductible.

Fenêtre de terminal
# 4. Se connecter (si pas déjà fait)
echo "$DOCKER_TOKEN" | docker login -u monuser --password-stdin
# 5. Pousser l'image
docker push monuser/monapp:v1.0.0
docker push monuser/monapp:latest

Résultat attendu :

The push refers to repository [docker.io/monuser/monapp]
a3b52c...: Pushed
b4c63d...: Pushed
v1.0.0: digest: sha256:abc123... size: 1234

Rendez-vous sur https://hub.docker.com/r/monuser/monapp pour voir votre image publiée avec ses tags.

Ces trois formes de référence n'offrent pas les mêmes garanties. Un tag est une étiquette mobile : nginx:1.25 peut désigner une image différente demain, après une reconstruction du mainteneur pour corriger une faille. Un digest sha256: désigne un contenu unique et vérifié à la réception, c'est la seule référence réellement immuable, donc celle à utiliser en production. La contrepartie est qu'aucune correction de sécurité ne vous parviendra sans mise à jour explicite du digest.

Fenêtre de terminal
# Pull d'une image officielle
docker pull nginx:1.25
# Pull d'une image utilisateur
docker pull monuser/monapp:v1.0.0
# Pull avec digest (immuable, recommandé en production)
docker pull nginx@sha256:a3b52c...

L'approche moderne consiste à builder et pusher automatiquement vos images à chaque commit. Voici un workflow complet avec scan de sécurité.

Workflow CI/CD : GitHub → GitHub Actions (build + scan) → Docker Hub →
Production

Pour que GitHub Actions puisse pousser vos images, il a besoin de vos identifiants Docker Hub. Ne jamais les mettre en clair dans le workflow : utilisez les secrets GitHub qui sont chiffrés et injectés uniquement au runtime.

Dans votre repo GitHub : SettingsSecrets and variablesActionsNew repository secret :

SecretValeur
DOCKERHUB_USERNAMEVotre username Docker Hub
DOCKERHUB_TOKENVotre PAT avec permissions Read & Write

Créez .github/workflows/docker-publish.yml. Trois réglages de ce fichier sont des exigences de sécurité et non des préférences : le bloc permissions: {} au niveau du workflow, qui retire par défaut tout droit au GITHUB_TOKEN et oblige à redonner explicitement le strict nécessaire par job ; persist-credentials: false sur le checkout, sans quoi le jeton reste écrit dans .git/config et devient lisible par n'importe quelle étape ultérieure ou dépendance de build ; et le SHA complet sur chaque action. Notez aussi le push: ${{ github.event_name != 'pull_request' }} : une pull request venue d'un fork ne doit jamais pouvoir publier une image.

name: Build and Push Docker Image
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
permissions: {}
env:
REGISTRY: docker.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
# Toutes les actions sont pinées par SHA pour la sécurité
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Login to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@06fb636fac595d6fb4b28a5dfcb21a6f5091859c # v4.5.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha,prefix=
- name: Build and push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Run Docker Scout scan
if: github.event_name != 'pull_request'
uses: docker/scout-action@2688993af7bafd6ba8c6a74ec652442be91dd82b # v1.23.1
with:
command: cves
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
only-severities: critical,high
exit-code: true

L'enchaînement suit une logique de garde-fou : rien n'est publié tant que la construction n'a pas réussi, et rien ne reste publié si le scan trouve une faille grave. C'est le rôle de exit-code: true sur la dernière étape, qui fait échouer le job lorsque Docker Scout remonte une vulnérabilité critique ou haute. Attention à l'ordre : l'image est poussée avant d'être scannée, donc une image vulnérable existe brièvement sur le registre. Pour l'éviter, poussez d'abord vers un dépôt de préproduction et ne promouvez qu'après le scan.

  1. Checkout : récupère le code source
  2. Buildx : active le builder moderne avec cache
  3. Login : s'authentifie avec le PAT (sauf pour les PR)
  4. Metadata : génère automatiquement les tags (main, v1.2.3, sha-abc123)
  5. Build & Push : construit et pousse l'image
  6. Scout : scanne les vulnérabilités et échoue si critique/haute

Pour les projets d'équipe, Docker Hub propose des organisations avec gestion des accès (RBAC).

Une organisation possède son propre namespace, distinct de votre compte personnel : vos images deviennent mon-entreprise/api et non plus votrenom/api. C'est la raison principale de la créer tôt, même à deux personnes, car migrer des images d'un namespace personnel vers une organisation impose de republier et de mettre à jour toutes les références de déploiement.

  1. Docker Hub → OrganizationsCreate Organization
  2. Choisissez un nom (ex: mon-entreprise)
  3. Sélectionnez le plan (Team requis pour RBAC avancé)

Le modèle de permissions de Docker Hub suit le principe du moindre privilège : chaque membre n'a accès qu'à ce dont il a besoin. Créez des teams par fonction (backend, frontend, devops) et assignez-leur des permissions spécifiques sur les repositories.

RôlePermissions
OwnerAccès total (facturation, membres, tous les repos)
MemberAccès aux repos assignés selon les teams
ReadPull uniquement
WritePull + Push
AdminPull + Push + Gestion du repo

Exemple de structure :

Organisation: mon-entreprise
├── Team: backend-devs
│ ├── Permissions: Write sur mon-entreprise/api-*
│ └── Membres: alice, bob
├── Team: devops
│ ├── Permissions: Admin sur tous les repos
│ └── Membres: charlie
└── Team: frontend-devs
├── Permissions: Read sur mon-entreprise/api-*
└── Membres: diana

Docker Hub compte les téléchargements sur une fenêtre glissante de 6 heures et applique le quota selon le compte authentifié, ou selon l'adresse IP quand personne n'est connecté. Les valeurs ci-dessous sont celles publiées par la documentation Docker (« Docker Hub pull usage and limits ») ; elles ont changé plusieurs fois depuis 2020, vérifiez-les avec la méthode donnée plus bas avant de dimensionner une CI.

Type d'utilisateurLimite par fenêtre de 6 heures
Anonyme (sans login)100 par adresse IPv4 ou par sous-réseau IPv6 /64
Personal (gratuit, authentifié)200
Pro, Team, BusinessIllimité

Deux subtilités de comptage expliquent la plupart des dépassements inattendus. D'abord, une image multi-architecture compte pour un téléchargement par architecture récupérée. Ensuite, une simple vérification de version, celle que fait docker pull quand l'image est déjà présente localement, n'est pas décomptée. Enfin, derrière un NAT d'entreprise ou sur un exécuteur CI mutualisé, tous les postes partagent la même adresse IP publique et donc le même quota anonyme : c'est le scénario le plus fréquent d'erreur 429 sans qu'aucun développeur n'ait abusé.

Le message renvoyé par le démon est explicite mais arrive souvent au milieu d'un journal de CI. Repérez le mot-clé toomanyrequests, qui correspond au code HTTP 429. Un point à ne pas confondre : ce message signale un quota épuisé, jamais un problème d'identifiants, contrairement à unauthorized qui pointe vers le token.

Error response from daemon: toomanyrequests: You have reached your pull
rate limit. You may increase the limit by authenticating...

Ces trois réponses se cumulent plutôt qu'elles ne s'excluent, et leur ordre correspond à leur rapport effet sur effort. S'authentifier ne coûte rien et double le quota. Le miroir de registre demande une infrastructure mais supprime la quasi-totalité des appels sortants, c'est la seule option qui tient sur une flotte d'exécuteurs CI. L'abonnement payant ne se justifie qu'après avoir mesuré que les deux premières ne suffisent pas.

La solution la plus simple : toujours se connecter, même pour les pulls.

Fenêtre de terminal
# Dans vos scripts/CI
echo "$DOCKERHUB_TOKEN" | docker login -u $DOCKERHUB_USERNAME --password-stdin
# Ensuite les pulls sont décomptés sur votre compte (200 par 6 h au lieu de 100
# partagés avec toutes les machines derrière la même adresse IP publique)
docker pull nginx:1.25

Le registre renvoie votre quota réel dans les en-têtes HTTP de la réponse, et c'est la seule source qui reflète votre situation à l'instant T. Lisez la valeur après le point-virgule : w=21600 désigne une fenêtre de 21 600 secondes, soit 6 heures. L'en-tête ratelimit-remaining donne ce qu'il vous reste, et docker-ratelimit-source révèle l'identité retenue par Docker, votre nom d'utilisateur si vous êtes authentifié, sinon l'adresse IP publique depuis laquelle la requête est partie. Cette dernière ligne suffit souvent à diagnostiquer un quota consommé par les collègues plutôt que par vous.

Fenêtre de terminal
# 1. Obtenir un jeton de lecture (anonyme ici, sans authentification)
TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/nginx:pull" | jq -r .token)
# 2. Interroger le manifeste et ne garder que les en-têtes de quota
curl -s -I -H "Authorization: Bearer $TOKEN" \
https://registry-1.docker.io/v2/library/nginx/manifests/latest \
| grep -i ratelimit
# Résultat exemple :
# docker-ratelimit-source: 203.0.113.42
# ratelimit-limit: 100;w=21600
# ratelimit-remaining: 95;w=21600

Pour mesurer le quota de votre compte et non celui de votre adresse IP, remplacez le jeton anonyme par un jeton authentifié en ajoutant -u "$DOCKERHUB_USERNAME:$DOCKERHUB_TOKEN" à la première commande.

Un secret écrit pendant la construction ne disparaît pas si vous le supprimez dans une instruction suivante : chaque RUN produit une couche conservée dans l'image, et docker history permet de les relire. Sur un dépôt public, considérez le secret comme divulgué dès la publication, donc à révoquer, pas seulement à retirer. Pour les secrets vraiment nécessaires à la construction, comme un jeton de dépôt privé de paquets, utilisez RUN --mount=type=secret de BuildKit, qui monte la valeur le temps de la commande sans l'inscrire dans une couche.

# ❌ MAUVAIS : le secret reste dans l'historique des layers
RUN echo "API_KEY=abc123" >> /app/.env
# ✅ BON : utiliser des variables d'environnement au runtime
# docker run -e API_KEY=abc123 monimage

Les deux commandes ci-dessous ne remontent pas exactement les mêmes résultats, et c'est normal : elles s'appuient sur des bases de vulnérabilités différentes. Docker Scout est intégré à la CLI Docker et bien connecté à Docker Hub, Trivy est open source, s'exécute hors ligne après un premier téléchargement de sa base et couvre en plus les dépendances applicatives. Dans les deux sorties, triez par sévérité et regardez d'abord la colonne du paquet fautif : la majorité des alertes critiques se règlent en changeant d'image de base, pas en corrigeant votre code.

Fenêtre de terminal
# Avec Docker Scout (intégré)
docker scout cves monuser/monapp:v1.0.0
# Avec Trivy (open source)
trivy image monuser/monapp:v1.0.0

Utiliser des tags versionnés, jamais latest en production

Section intitulée « Utiliser des tags versionnés, jamais latest en production »

Le tag latest n'a aucune signification particulière pour Docker : c'est le nom appliqué par défaut quand vous n'en précisez aucun, rien de plus. Il ne désigne ni la version la plus récente, ni la plus stable, et le mainteneur peut le repointer à tout moment. Conséquence concrète, deux serveurs déployés à quelques jours d'intervalle avec le même fichier Compose peuvent faire tourner deux images différentes, ce qui rend tout diagnostic de régression impossible. Le digest supprime totalement ce risque.

# ❌ MAUVAIS : latest peut changer à tout moment
image: monuser/monapp:latest
# ✅ BON : tag versionné ou digest
image: monuser/monapp:v1.2.3
image: monuser/monapp@sha256:abc123...

Chaque PAT devrait avoir exactement les permissions nécessaires, pas plus. Si un serveur de production n'a besoin que de récupérer des images, donnez-lui un token en lecture seule. Si ce token est compromis, l'attaquant ne pourra pas pousser d'images malveillantes.

UsagePermissions recommandées
CI/CD (push)Read & Write
Serveurs de prod (pull only)Read
Développeur localRead & Write
Scripts de nettoyageRead, Write & Delete

Voici les erreurs les plus fréquentes rencontrées avec Docker Hub et leurs solutions. Dans la majorité des cas, le problème vient de l'authentification (token expiré, mauvaises permissions) ou du rate limiting.

SymptômeCause probableSolution
unauthorized: authentication requiredToken invalide ou expiréRégénérer un PAT, refaire docker login
denied: requested access to the resource is deniedPermissions insuffisantesVérifier les permissions du PAT (Write pour push)
toomanyrequests (429)Rate limiting atteintS'authentifier, utiliser un miroir, ou passer à Pro
manifest unknownTag inexistantVérifier le nom exact du tag sur Docker Hub
unauthorized: please use personal access tokenMot de passe au lieu de PATCréer un PAT et l'utiliser à la place du mot de passe
  1. Docker Hub est la registry publique de référence avec plus de 100M d'images. Privilégiez les images avec badges (Official, Verified, Sponsored).

  2. Les PAT remplacent les mots de passe : obligatoires avec la double authentification et sous SSO imposé, recommandés partout ailleurs. Créez-en un avec les permissions minimales et stockez-le dans un gestionnaire de secrets. L'expiration ne se modifie pas après création.

  3. Le quota se compte sur 6 heures : 100 téléchargements par adresse IP en anonyme, 200 pour un compte Personal authentifié, illimité sur Pro, Team et Business. Connectez-vous toujours, même pour un simple pull, et lisez les en-têtes ratelimit-* pour connaître votre situation réelle.

  4. Automatisez vos builds avec GitHub Actions : build, scan avec Scout, push conditionnel. Utilisez les tags sémantiques et les digests.

  5. Scannez avant de publier avec Docker Scout ou Trivy. Ne poussez jamais d'image avec des vulnérabilités critiques.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn