Docker-out-of-Docker aka DooD
Maîtrisez Docker-out-of-Docker (DooD) pour lancer des commandes Docker depuis un container. Un guide pratique couvrant la mise en place pour root et pour un utilisateur non privilégié via le montage du socket Docker.
Traduction automatique
Journal des modifications

Cet article montre la technique Docker-out-of-Docker (DooD) — lancer des commandes Docker depuis l'intérieur d'un container en montant le /var/run/docker.sock de l'host — d'abord en tant que root (simple), puis avec un utilisateur non privilégié, ce qui exige en plus d'ajouter l'utilisateur du container à un groupe docker dont l'ID correspond à celui du groupe docker de l'host, via group_add dans compose.yaml.
Dans des situations très exceptionnelles, vous pourriez avoir besoin de lancer des commandes Docker depuis un container Docker. Attendez ? Quoi ?
Imaginez la situation suivante : vous faites tourner un container PHP, tout va bien, et vous voulez lancer un outil de qualité de code comme, commençons simple, phplint (voir cet article pour plus de détails). Vous ne voulez pas installer phplint parce que vous connaissez une image Docker très pratique qui le contient déjà (pensez à jakzal/phpqa).
Donc, vous êtes dans un container et vous voulez lancer un autre container.
Autre exemple : vous êtes toujours dans un container et vous voulez accéder à la liste des containers qui tournent, aux images Docker déjà installées, aux volumes, ... (pensez à portainer)
Voyons dans cet article comment créer votre propre image Docker, tournant en root ou pas, et la configurer pour autoriser les requêtes Docker.
L'usage réel le plus courant de cette technique est un pipeline CI dont le job doit lancer docker run ; c'est le sujet de GitLab - Exécuter Docker-out-of-Docker dans votre CI.
Nous allons utiliser la technique Docker-out-of-Docker, aussi appelée DooD : monter le socket Docker de l'host (/var/run/docker.sock) et lancer le CLI Docker dans le container.
Le résultat : des commandes Docker lancées depuis un container
Une fois le socket Docker de l'host monté dans un container, les commandes docker lancées depuis celui-ci atteignent directement le daemon de l'host — aucune installation Docker-in-Docker nécessaire :

docker image list fonctionne aussi, depuis le container, et liste chaque image déjà présente sur l'host. La suite de cet article construit le Dockerfile et le compose.yaml qui rendent cela possible, puis montre la même astuce avec un utilisateur non-root.
Pourquoi ça marche
- Le container ne fait jamais tourner son propre daemon Docker — il parle uniquement à celui qui tourne déjà sur l'host, via le
/var/run/docker.sockpartagé. - Ce socket n'est qu'un fichier Unix : le monter dans le container suffit, pas besoin de mode privilégié ni d'une installation complète Docker-in-Docker.
- N'importe quel CLI Docker installé dans le container devient un client du daemon de l'host —
docker version,docker ps,docker image listfonctionnent tous dès que le socket est là.
Installation
Comme toujours, nous allons construire un exemple pleinement fonctionnel.
Créez un dossier bidon et placez-vous dedans : mkdir /tmp/dood && cd $_.
Il nous faut un Dockerfile, créons-le.
# syntax=docker/dockerfile:1
FROM alpine:latest
# hadolint ignore=DL3008# Note: use docker.io and not docker if you are using another image than alpine (i.e. with apt-get and not apk)RUN apk add --no-cache docker
# hadolint ignore=DL3002USER rootCe Dockerfile crée une image basée sur Alpine Linux et y ajoute Docker en tant qu'outil disponible. L'image résultante peut exécuter des commandes Docker à l'intérieur d'un conteneur, utile pour les pipelines CI/CD ou les tâches d'orchestration de conteneurs.
Nous allons aussi utiliser un compose.yaml, créez ce fichier également :
# cspell:ignore doodname: "dood-as-root"services: dood: build: . tty: true working_dir: /src volumes: # We need to share the Docker socket: this technique is called "Docker-out-of-Docker" - /var/run/docker.sock:/var/run/docker.sockCe fichier Docker Compose configure un service appelé dood qui construit une image à partir du Dockerfile du répertoire courant et exécute un conteneur avec accès au socket Docker de l'hôte, ce qui permet au conteneur d'exécuter des commandes Docker — une technique appelée Docker-out-of-Docker.
Nous allons créer notre image Docker et un container avec cette seule commande : docker compose up --detach --build.
Et maintenant, entrons dans le container en lançant : docker compose exec dood /bin/sh.
En lançant docker version dans le container, vous obtenez la sortie montrée plus haut. Pour vérifier maintenant si vous pouvez accéder à la liste des images installées sur votre host (ce qui est en théorie impossible), lancez docker image list et ... ça marche.
Docker-out-of-Docker (DooD) est activé ; cool, mais pourquoi ?
Pour comprendre pourquoi, rouvrez votre fichier compose.yaml et mettez l'entrée volumes en commentaire comme illustré ci-dessous :
name: "dood-as-root"services: dood: build: . tty: true working_dir: /src // highlight-start # volumes: # # We need to share the Docker socket: this technique is called "Docker-out-of-Docker" # - /var/run/docker.sock:/var/run/docker.sock // highlight-endCe fichier Docker Compose définit un service appelé dood qui construit une image à partir du répertoire courant et la lance dans un conteneur interactif avec le répertoire /src comme espace de travail. Les lignes commentées montrent comment configurer Docker-out-of-Docker en montant le socket Docker, une technique permettant au conteneur d'accéder au daemon Docker de l'hôte.
Si vous êtes encore dans le container, tapez exit pour revenir à la console de votre host.
De là, reconstruisez l'image et le container en relançant docker compose up --detach --build && docker compose exec dood /bin/sh.
Une fois dans la console du container, tapez à nouveau docker version et boum.
Ça ne marche plus.
DooD doit pouvoir accéder au daemon Docker
Comme vous le voyez, vous devez partager votre socket Docker (c.-à-d. le fichier appelé /var/run/docker.sock sur votre host) avec le container.
Tapez exit à nouveau, quittez le container, remettez le fichier compose.yaml comme avant (retirez les lignes commentées) et relancez docker compose up --detach --build && docker compose exec dood /bin/sh.
De retour dans la console du container, vous pouvez donc lancer des commandes comme docker ps pour obtenir la liste des containers qui tournent sur l'host et, par exemple, en arrêter certains (par ex. docker container stop 68e41eee2efd ; possible seulement si DooD est correctement configuré).
Plus de démos : faire tourner le container avec un utilisateur non privilégié
Ça fonctionnait sans trop de difficultés parce que nous étions root. Nous avons démarré le container en root. Tapez simplement whoami dans le container pour le vérifier. Vous pouvez aussi taper id -u pour voir que votre ID utilisateur est 0 (root).
Et vous le savez, faire tourner des containers en root est une mauvaise idée. Créons donc un utilisateur spécifique.
Pour cela, nous devons mettre à jour nos fichiers.
# syntax=docker/dockerfile:1
# cspell:ignore groupid,johndoe,addgroup,adduser
ARG DOCKER_OS_GROUPID=1000ARG DOCKER_OS_USERID=1000ARG DOCKER_OS_USERNAME="johndoe"
FROM alpine:latest
# hadolint ignore=DL3008# Note: use docker.io and not docker if you are using another image than alpine (i.e. with apt-get and not apk)RUN apk add --no-cache docker
ARG DOCKER_OS_GROUPIDARG DOCKER_OS_USERIDARG DOCKER_OS_USERNAME
RUN set -e -x \ && mkdir -p "/home/${DOCKER_OS_USERNAME}" \ && addgroup -g "${DOCKER_OS_GROUPID}" "${DOCKER_OS_USERNAME}" \ # Create our application user && adduser -S -D -u "${DOCKER_OS_USERID}" -G "${DOCKER_OS_USERNAME}" -h "/home/${DOCKER_OS_USERNAME}" "${DOCKER_OS_USERNAME}" \ # And, finally, set the correct permissions to the home folder of our user && chown -R "${DOCKER_OS_USERNAME}":"${DOCKER_OS_GROUPID}" "/home/${DOCKER_OS_USERNAME}"
USER "${DOCKER_OS_USERNAME}"Ce Dockerfile crée une image Docker basée sur Alpine Linux qui installe Docker et configure un utilisateur non-root avec des paramètres personnalisables. L'image prépare un environnement sécurisé où les processus s'exécutent sous l'identité de cet utilisateur applicatif plutôt que sous root.
# cspell:ignore dood,groupid,johndoename: "dood-as-unprivileged"services: dood: build: args: - DOCKER_OS_GROUPID=${DOCKER_OS_GROUPID:-1000} - DOCKER_OS_USERID=${DOCKER_OS_USERID:-1000} - DOCKER_OS_USERNAME=${DOCKER_OS_USERNAME:-johndoe} user: "${DOCKER_OS_USERID:-1000}:${DOCKER_OS_GROUPID:-1000}" tty: true volumes: - /var/run/docker.sock:/var/run/docker.sockCe fichier Docker Compose configure un service appelé 'dood' qui s'exécute en tant qu'utilisateur non-root avec des identifiants personnalisables. Le service accède au daemon Docker de l'hôte via un socket partagé, permettant ainsi à un conteneur d'exécuter des commandes Docker avec les permissions d'un utilisateur spécifique plutôt que root.
Maintenant, construisez cette nouvelle image et entrez dans le container avec, toujours la même, cette commande : docker compose up --detach --build && docker compose exec dood /bin/sh.
En tapant whoami, on voit que nous ne sommes plus root mais johndoe, avec l'ID utilisateur 1000 (id -u) et l'ID de groupe 1000 (id -g).
Comme nous partageons toujours notre /var/run/docker.sock dans le fichier yaml, on s'attend à ce que Docker-out-of-Docker fonctionne encore ; non ?
Essayons : docker ps pour obtenir la liste des containers.
Tout semble correct mais ... pourquoi ?
Pour pouvoir utiliser DooD avec un utilisateur non privilégié, il faut faire attention à ceci : votre utilisateur doit être membre du groupe docker de l'host. Et compose.yaml offre un moyen simple de le faire.
Mettez à jour votre compose.yaml en ajoutant les deux lignes ci-dessous.
# cspell:ignore dood,groupid,johndoename: "dood-as-unprivileged"services: dood: build: args: - DOCKER_OS_GROUPID=${DOCKER_OS_GROUPID:-1000} - DOCKER_OS_USERID=${DOCKER_OS_USERID:-1000} - DOCKER_OS_USERNAME=${DOCKER_OS_USERNAME:-johndoe} user: "${DOCKER_OS_USERID:-1000}:${DOCKER_OS_GROUPID:-1000}" tty: true // highlight-next-line group_add: // highlight-next-line - ${DOCKER_GROUPID:-1001} volumes: - /var/run/docker.sock:/var/run/docker.sockCe fichier Docker Compose configure un service appelé dood qui s'exécute en tant qu'utilisateur non privilégié. Il passe des variables d'environnement au build pour définir l'ID utilisateur, l'ID groupe et le nom d'utilisateur, puis ajoute le conteneur à un groupe supplémentaire pour accéder au socket Docker.
Entrez une fois encore dans le container : docker compose up --detach --build && docker compose exec dood /bin/sh
Et réessayez docker ps ; ça marche. Vous avez à nouveau accès à toutes les commandes Docker, comme docker image list.
Sous le capot (passez ceci si vous voulez juste l'utiliser)
Toujours pas fonctionnel ?
Ça devrait marcher. Si ce n'est pas le cas, assurez-vous d'avoir la dernière version de Docker (celle que j'ai utilisée pour ce tutoriel est Docker Desktop v4.42.1).
Dans votre container, lancez ls -alh /var/run/docker.sock pour regarder les permissions du socket Docker à l'intérieur du container. Vous verrez que le fichier appartient à l'utilisateur root MAIS NE DEVRAIT PAS appartenir à root. Si vous voyez root pour l'utilisateur et pour le groupe, vous avez trouvé pourquoi ça ne marchait pas. Votre utilisateur non privilégié n'est pas membre du groupe root, mais il est bien membre du groupe docker (celui dont l'ID de groupe est 1001).
En lançant ls -alh /var/run/docker.sock, vous devriez voir 1001 (ou docker) pour le groupe.
C'est quoi ce groupe 1001 ?
Comme dit plus haut, pour pouvoir utiliser DooD avec un utilisateur non privilégié, vous devez être membre du groupe docker de l'host (pas du groupe docker que vous trouvez dans le container).
Une façon de récupérer cet ID est de lancer getent group docker | cut -d: -f3. Vous verrez très probablement 1001 puisque c'est l'ID standard de ce groupe.
Remarque
Comme vous l'avez vu, je n'ai pas codé l'ID en dur dans le fichier yaml proposé : j'ai défini une variable système appelée DOCKER_GROUPID et, si cette variable n'existe pas, j'utilise la valeur 1001.
Donc, pour rendre le script robuste, il suffit d'initialiser la variable DOCKER_GROUPID avant de construire l'image :
Les limites de sécurité de DooD
Monter /var/run/docker.sock est pratique, mais soyez honnête avec vous-même sur ce que cela accorde : tout processus capable d'atteindre ce socket peut demander au daemon de l'host de démarrer un nouveau container avec presque n'importe quel montage ou privilège, y compris un container qui monte le système de fichiers racine de l'host. En pratique, un accès DooD équivaut à un accès root sur l'host, appartenance à un groupe ou non. Réservez-le à des contextes de confiance (votre propre machine de dev, un runner CI que vous contrôlez) — ne l'exposez jamais à un container qui exécute du code tiers ou non fiable.
Conclusion
Utiliser Docker-out-of-Docker dans un container qui tourne en root est assez simple — il suffit d'installer docker lors du build de l'image et de monter votre socket Docker.
C'est moins simple avec un utilisateur non privilégié, mais ça devient facile une fois la bonne méthode trouvée : utiliser la propriété group_add et récupérer l'ID du groupe docker local.
Remarque
N'essayez pas group_add avec docker (le nom du groupe) au lieu de l'ID ; ça ne marchera pas.
L'endroit où cette technique est la plus utile est un runner CI : voir GitLab - Exécuter Docker-out-of-Docker dans votre CI pour la configuration spécifique à GitLab. Pour un usage quotidien du même montage de socket, lazydocker dans un container met un tableau de bord live de tous les containers de l'host à une commande de distance.
Remerciements 🙏
Merci à Metin Y. (meyay) qui m'a signalé une erreur dans la terminologie que j'utilisais. Dans ma première version, je parlais de la technique décrite ici comme Docker-in-Docker (dind) ; c'était incorrect.
En montant le socket Docker de l'host (/var/run/docker.sock), il s'agit bien de la technique Docker-out-of-Docker.





