Vos jobs CI construisent des images Docker, mais vous les poussez manuellement vers Docker Hub ? Vos docker pull échouent avec une erreur HTTP 429 parce que Docker Hub limite les téléchargements ? GitLab intègre deux registres au niveau de chaque projet, le Container Registry pour les images Docker et le Package Registry pour les packages applicatifs, ainsi qu'un Dependency Proxy au niveau du groupe pour mettre en cache les images Docker Hub. Ce guide vous montre comment les utiliser concrètement dans vos pipelines.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Construire et pousser une image Docker depuis un pipeline GitLab CI
- Publier un package npm ou Maven dans le Package Registry
- Configurer le Dependency Proxy pour éviter les quotas Docker Hub
- Nettoyer automatiquement les anciennes images avec les cleanup policies
- Choisir entre DinD, kaniko et Buildah selon ce que votre runner autorise
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Dès que votre pipeline produit un livrable, une image Docker, un package npm, un artefact Maven, vous avez besoin d'un endroit fiable pour le stocker, le versionner et le distribuer. Sans registre intégré, les équipes finissent par :
- Pousser manuellement vers Docker Hub avec des identifiants partagés
- Atteindre les quotas Docker Hub (100 pulls par 6 heures sans authentification, 200 avec un compte Personal)
- Perdre la traçabilité entre le code source et l'image déployée
- Multiplier les outils externes (Nexus, Harbor, Artifactory) pour un besoin que GitLab couvre nativement
En administration quotidienne, les registres GitLab servent à :
- Déployer depuis la CI : le job
buildpousse l'image, le jobdeployla tire, tout reste dans GitLab - Partager des images de base entre projets d'un même groupe
- Distribuer des packages internes (bibliothèques, outils CLI) sans publier sur npmjs.com
- Contourner les limites Docker Hub via le Dependency Proxy du groupe
Prérequis
Section intitulée « Prérequis »- Un projet GitLab avec un pipeline fonctionnel (voir Premier pipeline)
- Docker installé localement pour tester les builds (voir Docker : premiers pas)
- Comprendre les variables CI/CD (les variables
CI_REGISTRY_*sont injectées automatiquement) - Un runner avec l'executor Docker ou Shell (voir Runners)
Le Container Registry GitLab
Section intitulée « Le Container Registry GitLab »Ce que c'est
Section intitulée « Ce que c'est »Le Container Registry est un registre d'images Docker (compatible OCI) intégré à chaque projet GitLab. Il est activé par défaut sur GitLab.com et sur les instances autogérées correctement configurées.
Chaque image est stockée sous une URL prévisible :
registry.gitlab.com/<namespace>/<projet>[/<sous-chemin>]:<tag>Par exemple, pour un projet mon-groupe/mon-app :
registry.gitlab.com/mon-groupe/mon-app:1.2.0registry.gitlab.com/mon-groupe/mon-app:latestregistry.gitlab.com/mon-groupe/mon-app/api:v3Variables d'authentification automatiques
Section intitulée « Variables d'authentification automatiques »Dans un pipeline GitLab CI, quatre variables liées au registre sont injectées automatiquement dans chaque job, sans aucune configuration de votre part :
| Variable | Valeur | Usage |
|---|---|---|
CI_REGISTRY | registry.gitlab.com | Adresse du registre |
CI_REGISTRY_USER | gitlab-ci-token | Utilisateur (token temporaire) |
CI_REGISTRY_PASSWORD | Token du job en cours | Mot de passe (expire avec le job) |
CI_REGISTRY_IMAGE | registry.gitlab.com/<namespace>/<projet> | URL complète de l'image |
Vous n'avez jamais besoin de stocker des identifiants dans vos variables CI/CD pour pousser vers le Container Registry de votre projet.
Construire et pousser avec Docker-in-Docker (DinD)
Section intitulée « Construire et pousser avec Docker-in-Docker (DinD) »La méthode classique utilise le service docker:dind (Docker-in-Docker) pour disposer d'un daemon Docker dans le job :
stages: - build
build-image: stage: build image: docker:27@sha256:aa3df78ecf320f5fafdce71c659f1629e96e9de0968305fe1de670e0ca9176ce services: - docker:27-dind@sha256:aa3df78ecf320f5fafdce71c659f1629e96e9de0968305fe1de670e0ca9176ce variables: DOCKER_TLS_CERTDIR: "/certs" before_script: - echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin script: - docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" -t "$CI_REGISTRY_IMAGE:latest" . - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" - docker push "$CI_REGISTRY_IMAGE:latest"Vérification : après le push, allez dans Deploy > Container Registry dans votre projet GitLab. Vous devez voir votre image avec les deux tags.
Construire sans DinD : kaniko ou Buildah
Section intitulée « Construire sans DinD : kaniko ou Buildah »Un constructeur sans démon lit le Dockerfile et produit l'image sans
démarrer de démon Docker : plus besoin du mode privileged. Mais « sans
privilège » ne veut pas dire « sans réglage ». Sur un runner à executor
Docker laissé avec ses profils de sécurité par défaut, le build du Dockerfile
du fil rouge Pipeline Craft donne deux
résultats opposés, que la documentation GitLab ne précise pas.
kaniko construit l'image sans aucun réglage du runner. Le dépôt
d'origine de Google est archivé depuis juin 2025, mais le projet continue
dans un fork maintenu, osscontainertools/kaniko, dont l'image -debug
embarque le shell dont GitLab a besoin pour exécuter script: :
build-image-kaniko: stage: build image: name: ghcr.io/osscontainertools/kaniko:v1.28.6-debug@sha256:7ab44d0131c3051b6dea128067e258c7afb5c4815082a93b9cf6c624f2140bf0 entrypoint: [""] before_script: - mkdir -p /kaniko/.docker - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json script: - /kaniko/executor --context "$CI_PROJECT_DIR" --dockerfile "$CI_PROJECT_DIR/Dockerfile" --destination "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"entrypoint: [""] est obligatoire : sans lui, GitLab lance l'entrypoint de
l'image au lieu de votre script. Le before_script écrit les identifiants
du registre dans /kaniko/.docker/config.json à partir des variables
CI_REGISTRY_* : sans ce fichier, le push échoue sur
UNAUTHORIZED: authentication required. Une limite à connaître : sur un
runner durci qui retire toutes les capacités Linux (cap_drop = ["ALL"]),
kaniko échoue sur chown /etc/gshadow: operation not permitted ; il lui faut
au minimum CHOWN, DAC_OVERRIDE et FOWNER pour ce Dockerfile, comme le
montre le guide Durcir vos pipelines.
Buildah, lui, échoue avec les profils par défaut :
Error during unshare(CLONE_NEWUSER): Operation not permitted. Il ne passe
qu'en assouplissant les profils seccomp et AppArmor du conteneur, deux
filtres qui limitent ce qu'un conteneur peut demander au noyau. Ce réglage se
fait dans le config.toml d'un runner dédié aux builds, jamais d'un runner
partagé :
[[runners]] executor = "docker" [runners.docker] privileged = false security_opt = ["seccomp:unconfined", "apparmor:unconfined"]Avec ce runner, le job Buildah suivant construit et pousse l'image.
STORAGE_DRIVER: vfs et BUILDAH_ISOLATION: chroot évitent les montages
overlay et les namespaces qu'un conteneur ne peut pas créer :
build-image-buildah: stage: build image: name: quay.io/buildah/stable:v1.42.0@sha256:239c3349daf9932c29d0d3e74393a180ae8a70c1f1db685a599a160cae01aeb8 variables: STORAGE_DRIVER: vfs BUILDAH_FORMAT: docker BUILDAH_ISOLATION: chroot before_script: - echo "$CI_REGISTRY_PASSWORD" | buildah login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" script: - buildah bud --tag "${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}" --tag "${CI_REGISTRY_IMAGE}:latest" . - buildah push "${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}" - buildah push "${CI_REGISTRY_IMAGE}:latest"Retirer seccomp et AppArmor reste une perte de protection, plus faible
que privileged mais réelle : pour BuildKit rootless, qui a le même besoin, la
documentation GitLab
déconseille seccomp:unconfined et recommande un profil seccomp sur mesure.
Sur Kubernetes ou OpenShift, GitLab documente un
tutoriel Buildah rootless
propre à ces plateformes.
Quelle méthode choisir ? Le tableau résume ce que chaque méthode exige du
runner, critère qui décide en pratique. Aucune ne rend sûr un code non
fiable : toutes exécutent les instructions RUN du Dockerfile.
| Critère | DinD | kaniko (fork maintenu) | Buildah |
|---|---|---|---|
Mode privileged requis | Oui | Non | Non |
| Réglage du runner (executor Docker) | privileged = true | Aucun, sauf si le runner retire toutes les capacités | security_opt seccomp et AppArmor assouplis |
| Démon Docker nécessaire | Oui | Non | Non |
| Format de sortie | Docker | OCI ou Docker | OCI ou Docker |
| Documentation GitLab | Oui | Retirée | Oui |
| Cas typique | Runners hébergés gitlab.com, runner dédié jetable | Runner Docker standard | Runner de build dédié, Kubernetes ou OpenShift |
Tagger les images avec stratégie
Section intitulée « Tagger les images avec stratégie »Un bon système de tags facilite le déploiement et le rollback :
build-image: stage: build image: docker:27@sha256:aa3df78ecf320f5fafdce71c659f1629e96e9de0968305fe1de670e0ca9176ce services: - docker:27-dind@sha256:aa3df78ecf320f5fafdce71c659f1629e96e9de0968305fe1de670e0ca9176ce variables: DOCKER_TLS_CERTDIR: "/certs" before_script: - echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin script: - | # Build une seule fois docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
# Ajouter des tags supplémentaires sans rebuilder docker tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
# Tag sémantique (si on builde un tag Git) if [ -n "$CI_COMMIT_TAG" ]; then docker tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG" fi
docker push --all-tags "$CI_REGISTRY_IMAGE"| Stratégie | Variable | Exemple | Usage |
|---|---|---|---|
| Commit SHA | $CI_COMMIT_SHORT_SHA | a1b2c3d4 | Traçabilité exacte |
| Branche | $CI_COMMIT_REF_SLUG | feature-auth | Staging, review apps |
| Tag Git | $CI_COMMIT_TAG | v1.2.0 | Production, releases |
latest | Sans objet | latest | Développement local |
Nettoyage automatique (cleanup policies)
Section intitulée « Nettoyage automatique (cleanup policies) »Sans nettoyage, le Container Registry grossit indéfiniment. GitLab propose des cleanup policies automatiques :
- Allez dans Settings > Packages and registries > Container Registry
- Activez Cleanup policy
- Configurez les règles :
| Paramètre | Recommandation | Effet |
|---|---|---|
| Expiration policy | Activée | Nettoie selon les règles ci-dessous |
| Older than | 90 jours | Supprime les images de plus de 90 jours |
| Keep n tags | 10 | Garde toujours les 10 derniers tags |
| Name regex (keep) | ^v\d+\.\d+\.\d+$ | Protège les tags sémantiques |
| Name regex (remove) | .* | Supprime tout ce qui n'est pas protégé |
Vous pouvez aussi déclencher le nettoyage via l'API :
curl --request DELETE \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/registry/repositories/$REPO_ID/tags/old-tag"Le Package Registry GitLab
Section intitulée « Le Package Registry GitLab »Ce que c'est
Section intitulée « Ce que c'est »Le Package Registry permet de publier et consommer des packages applicatifs directement dans GitLab. Il supporte :
| Format | Cas d'usage | Commande de publication |
|---|---|---|
| npm | Bibliothèques JavaScript/TypeScript | npm publish |
| Maven | Artefacts Java | mvn deploy |
| PyPI | Packages Python | twine upload |
| NuGet | Bibliothèques .NET | dotnet nuget push |
| Go | Modules Go | Proxy natif |
| Composer | Packages PHP | Via API |
| Conan | Bibliothèques C/C++ | conan upload |
| Helm | Charts Kubernetes | helm push |
| Generic | Tout fichier binaire | Via API HTTP |
Publier un package npm depuis la CI
Section intitulée « Publier un package npm depuis la CI »La publication tient en trois gestes : indiquer à npm où se trouve le registre GitLab, déclarer le nom du package avec son scope (le préfixe @mon-scope), puis lancer la publication depuis un job.
-
Configurez
.npmrcdans votre projet :@mon-scope:registry=https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm///gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN} -
Ajoutez le scope dans
package.json:{"name": "@mon-scope/ma-lib","version": "1.0.0"} -
Publiez depuis le pipeline :
publish-npm:stage: deployimage: node:22-alpine@sha256:16e22a550f3863206a3f701448c45f7912c6896a62de43add43bb9c86130c3e2script:- npm publishrules:- if: $CI_COMMIT_TAG
Vérification : allez dans Deploy > Package Registry dans votre projet. Vous devez voir votre package avec la version correspondante.
Publier un package Maven depuis la CI
Section intitulée « Publier un package Maven depuis la CI »Le principe est le même : le job lance mvn deploy, et Maven s'authentifie auprès de GitLab avec le token du job. Ce token passe par un en-tête HTTP Job-Token, déclaré dans un fichier de configuration Maven, ci_settings.xml, présenté juste après le job.
publish-maven: stage: deploy image: maven:3.9-eclipse-temurin-21@sha256:c2a2c58516d160f43b50f12baa427ca86989e0bc942609e04aff61da5d9a7d74 script: - mvn deploy -s ci_settings.xml rules: - if: $CI_COMMIT_TAGAvec un fichier ci_settings.xml :
<settings> <servers> <server> <id>gitlab-maven</id> <configuration> <httpHeaders> <property> <name>Job-Token</name> <value>${CI_JOB_TOKEN}</value> </property> </httpHeaders> </configuration> </server> </servers></settings>Le Dependency Proxy
Section intitulée « Le Dependency Proxy »Ce que c'est
Section intitulée « Ce que c'est »Le Dependency Proxy est un cache/miroir transparent pour Docker Hub, activé au niveau du groupe GitLab. Quand un runner tire une image publique (alpine, node, python), le proxy la met en cache dans GitLab. Les pulls suivants sont servis depuis le cache, sans toucher Docker Hub.
Pourquoi c'est critique
Section intitulée « Pourquoi c'est critique »Docker Hub applique des quotas de téléchargement calculés sur une fenêtre glissante de 6 heures :
| Type de compte | Limite par 6 heures |
|---|---|
| Non authentifié | 100 pulls par adresse IPv4 (ou sous-réseau IPv6 /64) |
| Personal (authentifié) | 200 pulls |
| Pro / Team / Business | Illimité |
Sur un runner partagé avec plusieurs projets, la limite de 100 pulls par 6 heures (par adresse IP) est atteinte rapidement. Le Dependency Proxy contourne ce problème en servant les images depuis le cache GitLab.
Configurer le Dependency Proxy
Section intitulée « Configurer le Dependency Proxy »Il suffit d'activer le proxy sur le groupe, puis de préfixer le nom des images par l'adresse du proxy dans vos jobs.
-
Activez-le au niveau du groupe : Settings > Packages and registries > Dependency Proxy
-
Utilisez l'URL du proxy dans vos pipelines :
# Avant (Docker Hub direct, soumis aux quotas)image: alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6# Après (via Dependency Proxy, cache du groupe)image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 -
Pour toutes les images d'un pipeline, utilisez une variable globale :
variables:DOCKER_IMAGE_PREFIX: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}test:image: ${DOCKER_IMAGE_PREFIX}/node:22-alpine@sha256:16e22a550f3863206a3f701448c45f7912c6896a62de43add43bb9c86130c3e2script:- npm testlint:image: ${DOCKER_IMAGE_PREFIX}/python:3.12-slim@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710descript:- ruff check .
Vérification : après un premier pull, allez dans Operate > Dependency Proxy au niveau du groupe. Vous devez voir les images mises en cache.
Utiliser des images du Container Registry dans vos pipelines
Section intitulée « Utiliser des images du Container Registry dans vos pipelines »Une fois vos images construites et poussées, utilisez-les comme images de base dans d'autres jobs ou projets :
# Image du même projet, construite par ce committest: image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA script: - pytest
# Image d'un autre projet du même groupelint: image: registry.gitlab.com/mon-groupe/outils-ci/linter:2.1.0 script: - run-linter .Pour tirer une image d'un autre projet, deux options :
Option 1, CI_JOB_TOKEN + allowlist (recommandé) : le token du job en cours peut accéder au registre d'un autre projet si celui-ci l'autorise explicitement dans sa job token allowlist (Settings > CI/CD > Token Access). Aucun secret supplémentaire à gérer :
# Rien de spécial côté pipeline, le runner s'authentifie automatiquement# À condition que le projet source ait ajouté votre projet à sa job token allowlistlint: image: registry.gitlab.com/mon-groupe/outils-ci/linter:2.1.0 script: - run-linter .Option 2, Deploy Token : pour un accès stable indépendant des pipelines (pulls depuis Docker Desktop, scripts externes), créez un Deploy Token avec la permission read_registry dans le projet source :
# Dans les variables CI/CD du projet consommateurDOCKER_AUTH_CONFIG: > {"auths":{"registry.gitlab.com":{"auth":"<base64(username:token)>"}}}Dépannage
Section intitulée « Dépannage »Le tableau suit l'ordre du guide : lisez le message d'erreur du job, repérez-le dans la colonne de gauche, puis appliquez la solution correspondante.
| Symptôme | Cause probable | Solution |
|---|---|---|
denied: access forbidden lors du push | Runner non authentifié | Vérifiez docker login avec CI_REGISTRY_* |
error: toomanyrequests | Quota Docker Hub atteint | Activez le Dependency Proxy ou authentifiez Docker Hub |
| Image poussée mais invisible dans l'UI | Registre désactivé sur le projet | Settings > General > Visibility > Container Registry |
manifest unknown sur le pull | Tag inexistant ou cleanup policy trop agressive | Vérifiez le regex "keep" dans les cleanup policies |
Build Buildah échoue avec STORAGE_DRIVER | Driver overlay non supporté dans le conteneur | Utilisez STORAGE_DRIVER: vfs et BUILDAH_ISOLATION: chroot |
Buildah : unshare(CLONE_NEWUSER): Operation not permitted | Profils seccomp et AppArmor par défaut du runner | Runner de build dédié avec security_opt assoupli, ou kaniko |
kaniko : chown /etc/gshadow: operation not permitted | Runner qui retire toutes les capacités (cap_drop = ["ALL"]) | Rendre CHOWN, DAC_OVERRIDE et FOWNER au runner de build |
Cannot connect to the Docker daemon avec DinD | Runner en privileged = false | Runner dédié en privileged = true, ou kaniko |
Package npm 403 Forbidden | Scope manquant ou .npmrc mal configuré | Vérifiez que @scope correspond au namespace GitLab |
À retenir
Section intitulée « À retenir »- Le Container Registry est intégré à chaque projet GitLab, pas besoin de Docker Hub pour vos propres images
- Les identifiants du registre sont fournis à chaque job par
CI_REGISTRY_USERetCI_REGISTRY_PASSWORD: il reste à les passer à l'outil (docker login,buildah loginou leconfig.jsonde kaniko) - Sans mode
privileged, kaniko (fork maintenu) construit sur un runner Docker standard, Buildah exige des profils seccomp et AppArmor assouplis : choisissez selon ce que votre runner autorise - Le Dependency Proxy met en cache les images Docker Hub au niveau du groupe, indispensable face aux quotas (100 pulls/6h non-auth)
- Le Package Registry supporte npm, Maven, PyPI, NuGet, Go, Helm et bien d'autres formats
- Les cleanup policies évitent que votre registre grossisse indéfiniment, protégez vos tags de production
- L'accès cross-projet privilégie le
CI_JOB_TOKEN+ allowlist ; les Deploy Tokens servent pour les accès hors pipeline
On passe à la pratique
Section intitulée « On passe à la pratique »Mettez ce guide en application dans le Lab 09, Publier registry.