Introduction
Expédier des images Docker via GitLab CI/CD offre un chemin reproductible et auditable depuis le code jusqu'à un conteneur en cours d'exécution. Dans ce guide, vous allez mettre en place un pipeline concret qui construit et publie des images, comprendre les compromis de Docker-in-Docker, activer BuildKit pour accélérer les builds, appliquer un schéma de tags et une stratégie de cache fiables, puis piloter le tout en toute sécurité sur un GitLab Runner local avant de l'utiliser en production.
Ce texte conserve les concepts clés recherchables du domaine (GitLab CI Docker, Docker-in-Docker, BuildKit GitLab CI, GitLab Container Registry, Docker image pipeline, Dockerfile examples, docker commands, Docker Compose, local testing, production pitfalls, troubleshooting) pour rester ancré dans les pratiques industrielles.
Vue d'ensemble du workflow
Un workflow de production utile reste simple et explicite :
- Le développeur pousse du code dans GitLab.
- GitLab CI construit une image Docker avec BuildKit.
- Le job se connecte au GitLab Container Registry.
- Le job tague l'image avec le commit, la branche et, optionnellement, latest.
- Le job pousse tous les tags vers le registre.
- Optionnel : un job de déploiement tire l'image par tag et exécute le conteneur.
Préparer les fichiers du projet
- Créez un Dockerfile optimisé pour le cache.
# syntax=docker/dockerfile:1.7
FROM node:20-alpine AS base
WORKDIR /app
# Installer les dépendances prod avec cache npm
COPY package*.json ./
RUN --mount=type=cache, target=/root/.npm npm ci --omit=dev
# Copier le reste en dernier pour maximiser la réutilisation des couches
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
- Ajoutez un .dockerignore pour garder un contexte de build réduit :
node_modules
.git
.gitlab
Dockerfile*
*.log
- Facultatif : ajoutez un docker-compose.yml pour les tests locaux :
services:
web:
build: .
ports:
- "3000:3000"
Choix de stratégie de build
Vous avez deux façons courantes d'exécuter des builds Docker dans GitLab CI :
A) Docker-in-Docker (DinD)
- Fonctionnement : l'image du job est
docker: XXet un servicedocker: dindexécute le démon Docker dans l'environnement du job. - Avantages : portable, auto-contenu, cohérent entre runners.
- Inconvénients : nécessite un runner privilégié, démarrage potentiellement plus lent, surcoût réseau.
B) Socket Docker de l'hôte (shell ou exécuteur Docker avec /var/run/docker.sock)
- Fonctionnement : le job parle au démon Docker de l'hôte.
- Avantages : builds souvent plus rapides, pas de configuration DinD.
- Inconvénients : accès plus large à l'hôte, durcissement du runner requis.
Choisissez selon votre politique de sécurité et votre posture de risque. Pour démarrer, DinD est un défaut simple. À l'échelle, le socket hôte peut améliorer les performances si vous acceptez les compromis de sécurité.
Activer BuildKit
BuildKit accélère les builds, améliore le caching et permet des fonctionnalités comme les mounts de cache et la gestion de secrets. Activez-le avec DOCKER_BUILDKIT=1 et utilisez docker buildx pour les options avancées de cache.
Conseils clés :
- Utilisez la directive
# syntax=dans votre Dockerfile pour les fonctionnalités modernes. - Placez
COPYdes manifests de dépendances avant les fichiers sources pour réutiliser les couches d'installation. - Employez
--mount=type=cachedans les étapesRUNqui téléchargent des paquets.
Configuration du pipeline GitLab CI
Cet exemple emploie DinD, se connecte au GitLab Container Registry, applique des tags sûrs et pousse les images. Enregistrez-le dans .gitlab-ci.yml.
stages: [build, push]
image: docker:24
services:
- name: docker:24-dind
variables:
DOCKER_TLS_CERTDIR: ""
DOCKER_DRIVER: overlay2
DOCKER_BUILDKIT: "1"
IMAGE: $CI_REGISTRY_IMAGE
TAG_SHA: $CI_COMMIT_SHORT_SHA
TAG_BRANCH: $CI_COMMIT_REF_SLUG
TAG_LATEST: latest
before_script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
build:
stage: build
script:
- docker info
- docker buildx create --name ci-builder --use || true
- docker buildx inspect --bootstrap
- >
docker buildx build \
--pull \
--progress=plain \
--tag "$IMAGE:$TAG_SHA" \
--tag "$IMAGE:$TAG_BRANCH" \
--cache-from=type=registry, ref="$IMAGE:buildcache" \
--cache-to=type=registry, ref="$IMAGE:buildcache",mode=max \
--file Dockerfile . \
--load
push:
stage: push
script:
- docker push "$IMAGE:$TAG_SHA"
- docker push "$IMAGE:$TAG_BRANCH"
- |
if [ "$CI_COMMIT_REF_NAME" = "main" ] || [ "$CI_COMMIT_REF_NAME" = "master" ]; then
docker tag "$IMAGE:$TAG_SHA" "$IMAGE:$TAG_LATEST"
docker push "$IMAGE:$TAG_LATEST"
fi
Notes :
$CI_REGISTRY,$CI_REGISTRY_IMAGE,$CI_REGISTRY_USERet$CI_REGISTRY_PASSWORDsont fournis par GitLab lorsque le GitLab Container Registry est activé sur le projet.- Le premier build n'aura pas d'image de cache ; BuildKit l'indiquera mais poursuivra quand même.
- Si vous utilisez le Docker hôte au lieu de DinD, supprimez
services, assurez-vous que Docker est disponible sur le runner, et conservez les mêmes étapes de script.
Stratégie de cache
Le cache de couches est votre principal levier pour accélérer les builds.
Actions pratiques :
- Ordonnez les instructions du Dockerfile pour maximiser la réutilisation : installation des dépendances en premier, puis code applicatif.
- Utilisez les mounts de cache BuildKit pour les gestionnaires de paquets.
- Publiez et tirez un cache de registre avec
docker buildx --cache-toet--cache-from. - Gardez le contexte de build léger via
.dockerignorepour éviter d'invalider le cache.
Exemple d'arguments de cache déjà inclus ci-dessus :
--cache-from=type=registry, ref="$IMAGE:buildcache"
--cache-to=type=registry, ref="$IMAGE:buildcache",mode=max
Plan pilote local
Démarrez avec un pilote petit et mesurable afin de tout vérifier en local avant le déploiement plus large.
Portée du pilote :
- Un service simple avec un Dockerfile minimal.
- Indicateurs de succès : temps de build, taille d'image, temps de démarrage du conteneur, capacité à pousser et tirer.
Étapes :
- Construire en local avec BuildKit :
DOCKER_BUILDKIT=1 docker build -t app: dev .
- Exécuter en local avec Docker Compose :
docker compose up --build
Visitez http://localhost:3000 pour confirmer que l'application tourne.
- Installer un GitLab Runner local et exécuter le job de build :
gitlab-runner exec docker build # exécuteur docker
# ou
gitlab-runner exec shell build # exécuteur shell avec Docker hôte
- Pousser une branche de test dans GitLab et vérifier que l'image apparaît dans le registre du projet avec les tags attendus.
Gardez le pilote étroit, mesurable et facile à inspecter. Étendez à d'autres services seulement après avoir atteint vos métriques.
Dépannage et pièges
Problèmes courants et correctifs rapides :
- Échec de connexion : assurez-vous que le registre du projet est activé et que
$CI_REGISTRY_USERet$CI_REGISTRY_PASSWORDsont présents. Pour les branches protégées, vérifiez les permissions. - DinD nécessite un runner privilégié : configurez le runner pour autoriser les conteneurs privilégiés si les builds échouent avec des erreurs du démon Docker.
- Pas de cache au premier build : sans gravité. Le deuxième build sera plus rapide si les tags sont identiques et le contexte stable.
- Builds lents : améliorez
.dockerignore, optimisez l'ordre desCOPYet utilisez les mounts de cache BuildKit. Envisagez le Docker hôte si votre politique le permet. - Collisions de tags : utilisez le SHA de commit pour des tags immuables. Utilisez
latestseulement surmainoumaster. - Images volumineuses : passez à des bases
alpineoudistrolesslorsque c'est possible, et gardez les artefacts de build hors de l'image finale. - Secrets dans les images : ne bakez pas de secrets dans les couches. Utilisez des build args ou des variables d'environnement au runtime et vérifiez avec
docker history.
Conclusion
Vous disposez maintenant d'un pipeline GitLab CI Docker pratique qui construit et publie des images avec des tags pertinents, l'accélération BuildKit et une stratégie de cache côté registre. Démarrez par un pilote local, vérifiez vos métriques, puis étendez à davantage de services. Réévaluez votre choix de stratégie de build (Docker-in-Docker vs Docker hôte) à mesure que vous grandissez, et gardez un Dockerfile mince pour préserver des builds rapides et fiables.