Introduction
Expédier des images Docker via GitLab CI/CD vous offre un chemin répétable et auditable du code vers un conteneur en cours d'exécution. Dans ce guide, vous configurerez un pipeline pratique qui construit et publie des images, comprendrez les compromis du Docker-in-Docker, activerez BuildKit pour la vitesse, appliquerez un étiquetage et une mise en cache fiables, et testerez le tout en toute sécurité sur un GitLab Runner local avant de l'utiliser en production.
Aperçu du flux de travail
Un flux de travail de production typique se présente ainsi :
- Le développeur pousse du code vers GitLab (par exemple, une branche de fonctionnalité
feature/login). - GitLab CI construit une image Docker avec BuildKit.
- Le job se connecte au registre de conteneurs GitLab.
- Le job étiquette l'image avec le SHA du commit (
abc1234), le slug de la branche (feature-login), et optionnellementlatestpour la branche par défaut. - Le job pousse toutes les étiquettes vers le registre.
- Optionnel : un job de déploiement tire l'image par étiquette et lance le conteneur.
Préparer les fichiers du projet
- Créez un
Dockerfileoptimisé pour la mise en cache :
# syntax=docker/dockerfile:1.7
FROM node:20-alpine AS base
WORKDIR /app
# Installer les dépendances de prod avec le 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
.dockerignorepour garder le contexte de construction petit :
node_modules
.git
.gitlab
Dockerfile*
*.log
- Optionnellement, ajoutez un
docker-compose.ymlpour des tests locaux :
services:
web:
build: .
ports:
- "3000:3000"
Choix de la stratégie de construction
Vous avez deux façons courantes d'exécuter des constructions Docker dans GitLab CI :
A) Docker-in-Docker (DinD)
- Fonctionnement : l'image du job est
docker:24et un servicedocker:24-dindexécute le démon Docker à l'intérieur de l'environnement du job. - Avantages : portable, autonome, cohérent entre les runners.
- Inconvénients : nécessite un runner privilégié, peut être plus lent au démarrage, sauts réseau supplémentaires.
B) Socket Docker hôte (exécuteur shell ou Docker avec /var/run/docker.sock)
- Fonctionnement : le job communique avec le démon Docker de l'hôte.
- Avantages : constructions généralement plus rapides, pas de configuration DinD.
- Inconvénients : accès plus large au Docker de l'hôte, durcissement du runner plus strict requis.
Choisissez celle qui correspond à votre politique de runner et à votre posture de sécurité. Pour la plupart des équipes qui débutent, DinD est un défaut simple. À mesure que vous grandissez, le Docker hôte peut améliorer les performances si vous acceptez les compromis de sécurité.
Activer BuildKit
BuildKit accélère les constructions, améliore la mise en cache et active des fonctionnalités comme les montages de cache et les secrets. Activez-le en définissant DOCKER_BUILDKIT=1 et en utilisant docker buildx pour les options de cache avancées.
Points clés :
- Utilisez la directive de syntaxe dans votre Dockerfile pour les fonctionnalités modernes.
- Gardez le
COPYdes manifestes de paquets avant les fichiers sources pour réutiliser les couches de dépendances. - Utilisez
--mount=type=cachedans les étapesRUNqui téléchargent des paquets (comme montré dans le Dockerfile ci-dessus pour npm).
Configuration du pipeline GitLab CI
Cet exemple utilise DinD, se connecte au registre de conteneurs GitLab, applique des étiquettes sûres et pousse les images. Enregistrez sous .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
--push
--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 .
push:
stage: push
script:
- docker pull "$IMAGE:$TAG_SHA"
- docker tag "$IMAGE:$TAG_SHA" "$IMAGE:$TAG_LATEST"
- docker push "$IMAGE:$TAG_LATEST"
rules:
- if: $CI_COMMIT_REF_NAME == "main" || $CI_COMMIT_REF_NAME == "master"
Notes :
$CI_REGISTRY,$CI_REGISTRY_IMAGE,$CI_REGISTRY_USERet$CI_REGISTRY_PASSWORDsont fournis par GitLab quand le registre de conteneurs est activé sur le projet.- La première construction n'aura pas d'image de cache ; BuildKit avertira mais continuera.
- Si vous utilisez le Docker hôte au lieu de DinD, supprimez
services, assurez-vous que votre runner a Docker disponible, et gardez les mêmes étapes de script.
Stratégie de cache
La mise en cache des couches est votre principal levier pour des constructions plus rapides.
Étapes pratiques :
- Ordonnez les instructions du Dockerfile pour maximiser la réutilisation : installation des dépendances d'abord, puis code de l'application.
- Utilisez les montages de cache BuildKit pour les gestionnaires de paquets (ex. :
RUN --mount=type=cache,target=/root/.npm npm ci). - Publiez et tirez un cache de registre avec
buildx --cache-toet--cache-from(comme montré dans le pipeline). - Gardez le contexte de construction léger avec
.dockerignorepour éviter d'invalider le cache.
Plan pilote local
Commencez avec un pilote petit et facile à mesurer pour tout vérifier localement avant le déploiement.
Périmètre du pilote :
- Un petit service avec un Dockerfile simple.
- Métriques de succès : temps de construction, taille de l'image, temps de démarrage du conteneur, capacité à pousser et tirer.
Étapes :
- Construisez localement avec BuildKit :
DOCKER_BUILDKIT=1 docker build -t app:dev .
- Exécutez localement avec Docker Compose :
docker compose up --build
Visitez http://localhost:3000 pour confirmer que ça fonctionne.
- Installez un GitLab Runner local et lancez le job de construction :
gitlab-runner exec docker build # exécuteur docker
# ou
gitlab-runner exec shell build # exécuteur shell avec Docker hôte
- Poussez une branche de test vers GitLab et confirmez que l'image apparaît dans le registre du projet avec les étiquettes attendues (
$CI_COMMIT_SHORT_SHAet$CI_COMMIT_REF_SLUG).
Gardez le pilote étroit, mesurable et facile à inspecter. Étendez à plus de services seulement après avoir atteint les 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, confirmez que le runner a la permission de pousser. - DinD nécessite un runner privilégié : Si les constructions échouent avec des erreurs de démon, configurez le runner avec
privileged = truedansconfig.tomlou utilisez un runner qui autorise les conteneurs privilégiés. - Pas de cache sur la première construction : Sans danger à ignorer. La deuxième construction devrait être plus rapide si les étiquettes sont les mêmes et le contexte stable.
- Constructions lentes : Ajoutez ou améliorez
.dockerignore, gardez l'ordreCOPYoptimisé, et utilisez les montages de cache BuildKit. Envisagez le Docker hôte si autorisé. - Collisions d'étiquettes : Utilisez le SHA de commit pour des étiquettes immuables. Utilisez
latestseulement surmainoumaster. - Images volumineuses : Passez à des bases alpine ou distroless quand possible, et gardez les artefacts de construction hors de l'image finale.
- Secrets dans les images : Ne cuisez pas de secrets dans les couches. Utilisez des arguments de construction ou des variables d'environnement à l'exécution et vérifiez avec
docker history.
Conclusion
Vous avez maintenant un pipeline GitLab CI pratique qui construit et publie des images Docker avec des étiquettes sensées, l'accélération BuildKit et une stratégie de cache. Commencez avec le pilote local, vérifiez les métriques, puis étendez à plus de services. Réévaluez votre choix de stratégie de construction (DinD vs Docker hôte) à mesure que vous grandissez, et gardez le Dockerfile léger pour préserver des constructions rapides et fiables.