GitLab - Exécuter Docker-out-of-Docker dans votre CI
Découvrez comment mettre en place Docker-out-of-Docker (DooD) dans votre pipeline GitLab CI/CD. Ce guide explique comment configurer votre GitLab Runner pour partager le socket Docker de l'host et les fichiers du projet, afin que les containers puissent exécuter des commandes `docker run`.
Traduction automatique
Cet article a plus d'un an, mais il a été relu le 30 juillet 2026 — son contenu est toujours exact.

Pour exécuter des commandes Docker dans un job GitLab CI (Docker-out-of-Docker), vous devez configurer votre GitLab Runner pour qu'il partage le socket Docker de l'host. Cela se fait en modifiant le fichier /etc/gitlab-runner/config.toml du runner. Dans la section [[runners.docker]], vous devez ajouter "/var/run/docker.sock:/var/run/docker.sock" à la liste volumes. Le container de la CI peut alors communiquer avec le démon Docker de l'host. De plus, pour partager les fichiers du projet avec les containers imbriqués, mappez le répertoire builds en ajoutant également "/builds:/builds" aux volumes.
Au boulot, ma CI tourne avec Docker. Chaque fois que je pousse des modifications sur GitLab, une CI démarre : GitLab lance un container Docker, récupère mon code et exécute une série d'outils.
Le container démarré par GitLab peut être basé sur une image Docker PHP, Python ou simplement Alpine. GitLab récupère ensuite mon code dans le container et exécute par exemple un outil comme phplint : tout va bien.
Depuis quelques mois, je construis une image Docker pour exécuter Dagger (voir Dagger.io - Utiliser dagger pour automatiser vos workflows CI). Et là, ça se complique : mon container Docker GitLab CI devrait pouvoir exécuter docker run ... my_dagger_image et, par défaut, ce n'est pas possible.
Mais c'est encore pire quand, dans mon container Dagger, je dois lancer un autre container (un par outil).
Oui, vous avez devine : je me retrouve à devoir exécuter Docker depuis un container Docker qui tourne déjà dans un autre container.
Voyons comment résoudre ce problème.
Voici le résultat une fois le runner configuré : un job de CI qui exécute docker run — un container qui démarre un autre container — sans l'erreur « Cannot connect to the Docker daemon » que vous obtiendriez sinon.
Pourquoi ça fonctionne
- Le GitLab Runner partage son propre socket Docker (
/var/run/docker.sock) avec le container de la CI. Du coup,docker rundans le job parle au démon Docker de l'host au lieu d'avoir besoin du sien : aucun moteur Docker-in-Docker imbriqué à installer ou à maintenir. - Partager aussi le dossier
buildspermet à un container démarré depuis le job de CI de monter les mêmes fichiers de projet que ceux dans lesquels le job a été checkouté. Sans ça, des chemins comme$CI_PROJECT_DIRpointeraient vers un dossier invisible pour le nouveau container. - Ce sont deux lignes ajoutées à
config.toml, pas une autre image de CI ni un service Docker-in-Docker privilégié. La technique s'appelle Docker Socket Passthrough.
Comme prérequis, vous devez avoir accès au serveur où tourne votre instance GitLab (le GitLab Runner), puisque vous allez devoir modifier la configuration du runner.
Le fichier de configuration du runner
Sur mon serveur GitLab Runner, ma configuration actuelle /etc/gitlab-runner/config.toml définit Docker comme executor, grâce à cette entrée :
[[runners]] executor = "docker"Ce fichier TOML définit la configuration d'un runner GitLab CI/CD en spécifiant que ce runner doit utiliser l'exécuteur Docker pour lancer les jobs. C'est une section de tableau imbriqué qui permet de configurer plusieurs runners si nécessaire.
Donc, lors de l'exécution de ma CI, GitLab commence par créer un container Docker. L'image de base par défaut peut être forcée dans /etc/gitlab-runner/config.toml comme ci-dessous, ou alors vous précisez simplement l'image de base dans le .gitlab-ci.yml de votre projet avec le tag image (doc).
[[runners]] executor = "docker"
// highlight-next-line [[runners.docker]] // highlight-next-line image = "php8.4"Ce fichier TOML configure un runner GitLab CI avec l'exécuteur Docker. Il établit une section imbriquée pour les paramètres Docker du runner, en spécifiant que les jobs doivent s'exécuter dans une image Docker PHP 8.4.
Comprendre le processus
Pour cette partie, je vais prendre un exemple très basique. Bien sûr, il n'est pas nécessaire d'utiliser Docker-in-Docker pour une simple action « php lint ». Je me sers juste de ce scénario pour ne pas trop compliquer les choses.
Donc, sur la base du fichier config.toml ci-dessus, je peux avoir un fichier .gitlab-ci.yml comme celui-ci :
phplint: script: - find . -name "*.php" -print0 | xargs -0 -n1 php -lCette configuration GitLab CI exécute une vérification de syntaxe PHP sur tous les fichiers du projet. Le script recherche chaque fichier .php et le teste avec php -l pour s'assurer qu'il n'y a pas d'erreurs de syntaxe.
Il va récupérer tous les fichiers .php et exécuter php -l <filename.php> pour chacun d'eux, dans un container Docker basé sur PHP 8.4.
Simple et direct.
Exécuter Docker-out-of-Docker
Sur la base du même exemple, on peut aussi utiliser une image Docker existante, comme phpqa/jakzal (je lui ai dédié un article : Une image Docker qui fournit les outils d'analyse statique pour PHP) :
phplint: image: docker:28.1.1 variables: DOCKER_HOST: unix:///var/run/docker.sock DOCKER_TLS_CERTDIR: "" script: - docker run --rm --volume "$CI_PROJECT_DIR:/app" --workdir /app jakzal/phpqa phplint .Cette tâche GitLab CI exécute phplint, un outil de vérification de syntaxe PHP, sur tout le projet en utilisant Docker. Elle configure Docker pour communiquer avec le daemon Docker de l'hôte et lance un conteneur qui analyse tous les fichiers PHP du répertoire du projet.
Pour que ça fonctionne, il va falloir mettre à jour le fichier de configuration de notre serveur GitLab Runner :
[[runners]] executor = "docker"
[[runners.docker]] image = "php8.4"
// highlight-next-line volumes = [ ..., "/var/run/docker.sock:/var/run/docker.sock","/builds:/builds"]Ce bloc de configuration TOML définit un runner GitLab CI qui utilise Docker comme exécuteur. Il configure l'image Docker à utiliser (PHP 8.4) et monte des volumes spécifiques, notamment le socket Docker et le répertoire des builds, pour permettre au conteneur d'accéder à l'hôte Docker et aux fichiers du projet.
Attention
La syntaxe volumes = [ ..., "/var/run/docker.sock:/var/run/docker.sock","/builds:/builds"] utilisée ci-dessus signifie qu'il faut ajouter ces deux valeurs à la liste que vous avez déjà. Donc, si vous avez actuellement volumes = ["/cache", "/certs/client"], ajoutez les valeurs pour obtenir volumes = ["/cache", "/certs/client", "/var/run/docker.sock:/var/run/docker.sock","/builds:/builds"]
Il faut autoriser le démon Docker qui tourne sur le serveur GitLab Runner à partager son socket (le fichier /var/run/docker.sock) afin que le container Docker GitLab CI puisse exécuter des instructions docker run comme par exemple docker run --rm --volume "$CI_PROJECT_DIR:/app" --workdir /app jakzal/phpqa phplint .
Comme vous pouvez le voir, il y a aussi un flag --volume "$CI_PROJECT_DIR:/app". Cela va partager le dossier référencé par la variable $CI_PROJECT_DIR avec le container mais ici, attention.
Disons que $CI_PROJECT_DIR est initialisée à /builds/your-group/your-project. Ce /builds/your-group/your-project est celui de votre serveur GitLab Runner (c'est donc un dossier sur le serveur). Dans votre container Docker GitLab CI, /builds/your-group/your-project existe aussi mais, quand vous exécutez docker run --rm --volume "$CI_PROJECT_DIR:/app" --workdir /app jakzal/phpqa phplint ., vous essayez donc de partager le répertoire de l'host avec votre nouveau container et ça ne marchera pas sauf si vous l'avez autorisé.
Reportez-vous à la mise à jour déjà faite dans le fichier config.toml ci-dessus. Nous avons partagé "/var/run/docker.sock:/var/run/docker.sock" pour permettre le partage du socket Docker, mais nous avons aussi partagé "/builds:/builds" et ici, c'est pour partager le dossier builds.
Information
Cette technique s'appelle Docker Socket Passthrough.
Maintenant, ça va fonctionner : on partage notre démon Docker et aussi le dossier du serveur où notre codebase a été récupérée avant l'exécution de la CI.
Dès lors, on peut non seulement exécuter des instructions docker run [...] mais aussi partager notre dossier avec le container fraîchement créé.
Pourquoi j'ai besoin de ça ? Prenons un seul exemple : la génération de documentation. Je pousse ma documentation sous forme d'un ensemble de fichiers .md (dans un dossier appelé documentation) puis, pendant ma CI, j'exécute Quarto (voir Quarto - Comment j'ai construit un écosystème auto-documenté pour plus de 50 projets) pour traiter ces fichiers Markdown et, selon ma configuration (le fichier _quarto.yml), je peux générer des fichiers hors ligne (comme .docx, .pdf, ...) mais aussi un site HTML statique. Dans ce dernier cas, j'exporte le site statique quelque part (./public/documentation) puis je le publie avec GitLab pages.
Conclusion
Deux lignes dans config.toml — le socket Docker et le dossier builds — suffisent à transformer un job GitLab CI en un job capable de démarrer ses propres containers, sans moteur Docker-in-Docker imbriqué à installer ou à maintenir. C'est la même astuce qui rend une image Docker Bruno CLI ou un pipeline Dagger exécutable directement depuis votre CI, et c'est sur elle que je m'appuie aujourd'hui pour lancer le build de documentation Quarto comme un container depuis un container.
Chargement des commentaires…

