Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English
Read in English

Registries GitLab : construire et publier images et packages

  • 35 min

Par mis à jour le

Améliorer cette page

Logo GitLab

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.

  • 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

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 build pousse l'image, le job deploy la 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
  • 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 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.0
registry.gitlab.com/mon-groupe/mon-app:latest
registry.gitlab.com/mon-groupe/mon-app/api:v3

Dans un pipeline GitLab CI, quatre variables liées au registre sont injectées automatiquement dans chaque job, sans aucune configuration de votre part :

VariableValeurUsage
CI_REGISTRYregistry.gitlab.comAdresse du registre
CI_REGISTRY_USERgitlab-ci-tokenUtilisateur (token temporaire)
CI_REGISTRY_PASSWORDToken du job en coursMot de passe (expire avec le job)
CI_REGISTRY_IMAGEregistry.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.

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èreDinDkaniko (fork maintenu)Buildah
Mode privileged requisOuiNonNon
Réglage du runner (executor Docker)privileged = trueAucun, sauf si le runner retire toutes les capacitéssecurity_opt seccomp et AppArmor assouplis
Démon Docker nécessaireOuiNonNon
Format de sortieDockerOCI ou DockerOCI ou Docker
Documentation GitLabOuiRetiréeOui
Cas typiqueRunners hébergés gitlab.com, runner dédié jetableRunner Docker standardRunner de build dédié, Kubernetes ou OpenShift

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égieVariableExempleUsage
Commit SHA$CI_COMMIT_SHORT_SHAa1b2c3d4Traçabilité exacte
Branche$CI_COMMIT_REF_SLUGfeature-authStaging, review apps
Tag Git$CI_COMMIT_TAGv1.2.0Production, releases
latestSans objetlatestDéveloppement local

Sans nettoyage, le Container Registry grossit indéfiniment. GitLab propose des cleanup policies automatiques :

  1. Allez dans Settings > Packages and registries > Container Registry
  2. Activez Cleanup policy
  3. Configurez les règles :
ParamètreRecommandationEffet
Expiration policyActivéeNettoie selon les règles ci-dessous
Older than90 joursSupprime les images de plus de 90 jours
Keep n tags10Garde 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 :

Fenêtre de terminal
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 permet de publier et consommer des packages applicatifs directement dans GitLab. Il supporte :

FormatCas d'usageCommande de publication
npmBibliothèques JavaScript/TypeScriptnpm publish
MavenArtefacts Javamvn deploy
PyPIPackages Pythontwine upload
NuGetBibliothèques .NETdotnet nuget push
GoModules GoProxy natif
ComposerPackages PHPVia API
ConanBibliothèques C/C++conan upload
HelmCharts Kuberneteshelm push
GenericTout fichier binaireVia API HTTP

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.

  1. Configurez .npmrc dans 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}
  2. Ajoutez le scope dans package.json :

    {
    "name": "@mon-scope/ma-lib",
    "version": "1.0.0"
    }
  3. Publiez depuis le pipeline :

    publish-npm:
    stage: deploy
    image: node:22-alpine@sha256:16e22a550f3863206a3f701448c45f7912c6896a62de43add43bb9c86130c3e2
    script:
    - npm publish
    rules:
    - if: $CI_COMMIT_TAG

Vérification : allez dans Deploy > Package Registry dans votre projet. Vous devez voir votre package avec la version correspondante.

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_TAG

Avec 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 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.

Docker Hub applique des quotas de téléchargement calculés sur une fenêtre glissante de 6 heures :

Type de compteLimite par 6 heures
Non authentifié100 pulls par adresse IPv4 (ou sous-réseau IPv6 /64)
Personal (authentifié)200 pulls
Pro / Team / BusinessIllimité

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.

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.

  1. Activez-le au niveau du groupe : Settings > Packages and registries > Dependency Proxy

  2. 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
  3. 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:16e22a550f3863206a3f701448c45f7912c6896a62de43add43bb9c86130c3e2
    script:
    - npm test
    lint:
    image: ${DOCKER_IMAGE_PREFIX}/python:3.12-slim@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de
    script:
    - 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 commit
test:
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
script:
- pytest
# Image d'un autre projet du même groupe
lint:
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 allowlist
lint:
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 consommateur
DOCKER_AUTH_CONFIG: >
{"auths":{"registry.gitlab.com":{"auth":"<base64(username:token)>"}}}

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ômeCause probableSolution
denied: access forbidden lors du pushRunner non authentifiéVérifiez docker login avec CI_REGISTRY_*
error: toomanyrequestsQuota Docker Hub atteintActivez le Dependency Proxy ou authentifiez Docker Hub
Image poussée mais invisible dans l'UIRegistre désactivé sur le projetSettings > General > Visibility > Container Registry
manifest unknown sur le pullTag inexistant ou cleanup policy trop agressiveVérifiez le regex "keep" dans les cleanup policies
Build Buildah échoue avec STORAGE_DRIVERDriver overlay non supporté dans le conteneurUtilisez STORAGE_DRIVER: vfs et BUILDAH_ISOLATION: chroot
Buildah : unshare(CLONE_NEWUSER): Operation not permittedProfils seccomp et AppArmor par défaut du runnerRunner de build dédié avec security_opt assoupli, ou kaniko
kaniko : chown /etc/gshadow: operation not permittedRunner 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 DinDRunner en privileged = falseRunner dédié en privileged = true, ou kaniko
Package npm 403 ForbiddenScope manquant ou .npmrc mal configuréVérifiez que @scope correspond au namespace GitLab
  • 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_USER et CI_REGISTRY_PASSWORD : il reste à les passer à l'outil (docker login, buildah login ou le config.json de 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

Mettez ce guide en application dans le Lab 09, Publier registry.

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn