Article initialement publié en 2015, mis à jour en octobre 2026.
Cet article fait partie d'une série de billets portant sur Docker et son environnement :
- Tuto Docker : comprendre Docker (partie 1)
- Tuto Docker : démarrer Docker (partie 2)
- Tuto Docker : les commandes Docker (partie 3)
Sommaire
Introduction
Docker est un outil qui permet de gérer des conteneurs (vous le savez déjà grâce à l'article de découverte de Docker). Sa grande force, c'est de rendre simples des mécanismes du noyau Linux qui sont, à la base, plutôt obscurs.
Autour de notre baleine (Moby Dock pour les intimes), Docker repose sur une API, dont je vous parle dans la partie 3. Par-dessus cette API, on utilise le client en ligne de commande docker, qui permet d'exécuter facilement des commandes depuis notre terminal.
Si vous tapez docker dans un terminal, vous obtenez la liste des commandes disponibles (extrait) :
$ docker
Usage: docker [OPTIONS] COMMAND
Common Commands:
run Create and run a new container from an image
exec Execute a command in a running container
ps List containers
build Build an image from a Dockerfile
pull Download an image from a registry
push Upload an image to a registry
images List images
login Authenticate to a registry
logout Log out from a registry
search Search Docker Hub for images
version Show the Docker version information
info Display system-wide information
Management Commands:
builder Manage builds
compose* Docker Compose
container Manage containers
image Manage images
network Manage networks
system Manage Docker
volume Manage volumes
…
Les commandes sont rangées en deux familles. Les commandes courantes (run, ps, build…) sont des raccourcis. Les commandes de gestion regroupent les actions par type d'objet : docker container ls est équivalent à docker ps, docker image ls à docker images, etc. Les deux syntaxes fonctionnent, utilisez celle que vous préférez.
Et voilà, avec cette liste, vous avez tout en main pour gérer vos conteneurs et vos images. Facile, non ?
Docker et ses images
Jusque-là, je vous ai surtout parlé de conteneurs. Pourtant, les images sont d'une importance cruciale dans le fonctionnement de Docker.
Une image est un modèle en lecture seule à partir duquel on crée des conteneurs. On peut la voir comme un « moule » : elle contient un système de fichiers (une Debian minimale, par exemple), les dépendances de l'application, sa configuration et la commande à lancer par défaut. Un conteneur est toujours créé à partir d'une image.
Les images sont construites par couches (layers) empilées. Chaque couche correspond à une étape de construction : installer un paquet, copier un fichier… Une image peut aussi être construite à partir d'une autre : l'image officielle de Tomcat s'appuie sur une image Java, elle-même construite à partir d'une distribution Linux. Les héritages peuvent aller très loin !
Ce système est très économe : si dix images partagent la même image de base Debian, les couches Debian ne sont stockées qu'une seule fois sur votre disque.
Le point technique !
Quand vous lancez un conteneur, Docker ajoute au-dessus des couches de l'image une couche inscriptible, propre à ce conteneur. Toutes les modifications faites dans le conteneur (fichiers créés, modifiés, supprimés) vont dans cette couche. L'image, elle, n'est jamais modifiée. C'est pour ça que l'on peut lancer plusieurs conteneurs à partir de la même image sans qu'ils se marchent dessus.
Vous pouvez visualiser les couches d'une image avec la commande
docker history <image>.
Vous pouvez à tout moment consulter votre « bibliothèque » d'images avec la commande docker images (ou docker image ls). Au fil des projets, elle va grossir.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
Voilà, vous venez d'interagir une première fois avec Docker. N'espérez pas voir grand-chose : par défaut, vous ne possédez aucune image. Logique. Heureusement, d'autres ont déjà préparé des images prêtes à l'emploi !
Comment récupérer (pull) une image Docker ?
Il existe une plateforme maintenue par Docker sur laquelle tout le monde peut publier (push) et récupérer (pull) des images : c'est un peu le GitHub des images Docker ! Cette bibliothèque géante, c'est Docker Hub.
Vous y trouverez des images de distributions (Debian, Ubuntu, Alpine…) et de produits prêts à l'emploi (Nginx, PostgreSQL, PHP, Node.js…). Faites attention à la provenance des images que vous utilisez. Docker Hub les identifie avec des badges :
- Docker Official Image : images maintenues par Docker en lien avec les éditeurs (
debian,nginx,postgres…) ; - Verified Publisher : images publiées par des éditeurs vérifiés ;
- Sponsored OSS : projets open source soutenus par Docker.
Pour le reste, n'importe qui peut publier : soyez prudent, et privilégiez ces badges. Pour aller plus loin, des outils comme Docker Scout ou Trivy analysent vos images à la recherche de vulnérabilités connues.
Vous avez deux moyens de chercher une image : l'interface web de Docker Hub, ou la commande docker search. Utilisons la commande (parce que je suis un barbu) pour trouver des images Debian ayant au moins 10 étoiles :
$ docker search --filter stars=10 debian
NAME DESCRIPTION STARS OFFICIAL
debian Debian is a Linux distribution that's compos… … [OK]
…
Ce qui m'intéresse, c'est l'image Debian officielle : elle s'appelle simplement debian et porte le flag [OK] dans la colonne OFFICIAL. En avant, Moby Dock, pull cette image !
$ docker pull debian
Après quelques secondes de téléchargement, l'image est disponible sur votre machine. Vous pouvez le vérifier avec docker images.
Le point technique !
Il existe un autre moyen, moins utile au quotidien, de transférer une image sans passer par un registry :
docker savel'exporte dans une archive.tar, que l'on importe sur une autre machine avecdocker load. Pratique pour une machine sans accès Internet, par exemple.Ne confondez pas avec
docker export/docker import, qui travaillent sur le système de fichiers d'un conteneur et perdent l'historique des couches.
Comment créer une image ?
Nous voulons maintenant construire nos propres images. Il existe deux méthodes :
- le Dockerfile : un fichier texte qui décrit, étape par étape, comment construire l'image à partir d'une image de base. C'est la méthode à utiliser.
- lancer un conteneur, y faire des modifications à la main, puis les enregistrer dans une nouvelle image avec
docker commit.
La seconde méthode a l'air pratique, mais elle produit des images non reproductibles : personne ne sait exactement ce qui a été fait dedans, pas même vous dans six mois. Réservez-la au debug ponctuel. Pour voir ce qui a changé dans un conteneur par rapport à son image, utilisez docker diff <conteneur>.
Créer une image avec un Dockerfile
Ce fichier contient une suite d'instructions : c'est la recette que Docker suit pour construire votre image. La documentation officielle détaille l'ensemble des instructions disponibles.
Créons un fichier nommé Dockerfile (sans extension) dans le dossier de notre projet. C'est le nom attendu par défaut, mais l'option -f de docker build permet d'en utiliser un autre (Dockerfile.dev, par exemple). Pour notre exemple, nous partons d'une Debian 13 (Trixie) et nous y installons Nginx.
Détaillons notre Dockerfile, ligne par ligne :
-
J'indique l'image de départ. La variante
slimest une Debian allégée, idéale pour les conteneurs :FROM debian:trixie-slim -
J'ajoute des métadonnées, ici l'auteur de l'image. L'ancienne instruction
MAINTAINERest dépréciée, on utilise désormais unLABEL:LABEL org.opencontainers.image.authors="Wanadev <contact@wanadev.fr>" -
Je mets à jour la liste des paquets, j'installe Nginx, puis je supprime le cache d'
aptpour garder une image légère. Le tout dans une seule instructionRUN, donc une seule couche :RUN apt-get update \ && apt-get install -y --no-install-recommends nginx \ && rm -rf /var/lib/apt/lists/* -
Je copie ma configuration Nginx depuis mon projet vers l'image :
COPY nginx.conf /etc/nginx/nginx.conf -
Je définis le dossier de travail : c'est le dossier courant pour les instructions suivantes et au démarrage du conteneur :
WORKDIR /home/docker -
Je copie mon script de démarrage en lui donnant directement les droits d'exécution (plus besoin d'un
RUN chmodséparé) :COPY --chmod=744 service_start.sh script/service_start.sh -
Je définis le point d'entrée : le programme lancé au démarrage du conteneur. On utilise la forme « exec » (avec les crochets), pour que le script reçoive correctement les signaux d'arrêt envoyés par
docker stop:ENTRYPOINT ["/home/docker/script/service_start.sh"]
Voici le Dockerfile complet :
FROM debian:trixie-slim
LABEL org.opencontainers.image.authors="Wanadev <contact@wanadev.fr>"
RUN apt-get update \
&& apt-get install -y --no-install-recommends nginx \
&& rm -rf /var/lib/apt/lists/*
COPY nginx.conf /etc/nginx/nginx.conf
WORKDIR /home/docker
COPY --chmod=744 service_start.sh script/service_start.sh
ENTRYPOINT ["/home/docker/script/service_start.sh"]
Et un exemple de service_start.sh. Un conteneur s'arrête dès que son processus principal se termine : Nginx doit donc tourner au premier plan, et exec lui passe la main pour qu'il reçoive les signaux :
#!/bin/sh
set -e
# … vos éventuelles étapes d'initialisation …
exec nginx -g 'daemon off;'
Le point technique !
Dans la vraie vie, pour un simple Nginx, on partirait directement de l'image officielle
nginx, déjà optimisée. Nous construisons ici l'image à partir de Debian pour l'exemple.Pensez aussi à créer un fichier
.dockerignoreà côté du Dockerfile (même principe qu'un.gitignore) pour éviter d'envoyer au build des fichiers inutiles :.git,node_modules,vendor, fichiers.env…
Construire une image depuis un Dockerfile
Maintenant que nous avons un Dockerfile, construisons notre image. La commande magique est… docker build !
$ docker build -t mon-nginx:1.0 .
-t mon-nginx:1.0donne un nom (mon-nginx) et un tag (1.0) à l'image ;- le
.final n'est pas le chemin du Dockerfile, mais le contexte de build : le dossier dont le contenu est envoyé à Docker et dans lequel les instructionsCOPYvont chercher leurs fichiers. Par défaut, Docker cherche leDockerfileà la racine de ce contexte.
Pendant la construction, vous voyez défiler les étapes les unes après les autres. Le premier build est long, car rien n'a encore été fait. Relancez la même commande : cette fois, c'est quasi instantané ! (Essayez, pour voir !) Les étapes inchangées sont marquées CACHED.
Comment fonctionne le cache Docker ?
Le cache de docker build n'est pas magique (même s'il en a l'air), et il est facile à comprendre.
Chaque instruction du Dockerfile produit une couche. Au build suivant, Docker vérifie pour chaque instruction, dans l'ordre, si elle a changé :
- pour un
RUN, si le texte de la commande est identique ; - pour un
COPY, si les fichiers copiés ont changé.
Si rien n'a changé, Docker réutilise la couche en cache. Dès qu'une instruction change, sa couche et toutes les suivantes sont reconstruites.
D'où une règle d'or : placez en haut du Dockerfile ce qui change rarement (installation des paquets système, des dépendances) et en bas ce qui change souvent (le code de votre application). Par exemple, pour un projet PHP, on copie d'abord composer.json et composer.lock, on lance composer install, et on copie le reste du code seulement après. Ainsi, modifier une ligne de code ne relance pas l'installation des dépendances. Fortiche, la baleine !
Tagger une image, ou sortir vos images de l'anonymat
Toute image possède un identifiant unique (son IMAGE ID). On pourrait l'utiliser, mais il est bien plus pratique de donner des noms et des tags à nos images, au format nom:tag. Sans tag précisé, Docker utilise le tag latest.
Il y a plusieurs façons de nommer une image :
- au moment du build, avec l'option
-t(ou--tag) dedocker build; - après coup, avec la commande
docker tag; - au moment d'un
docker commit.
Attention, le nom d'une image doit être en minuscules (mon-image, et non MonImage) :
$ docker build -t mon-image:dev .
$ docker tag mon-image:dev mon-image:1.0
Faites un docker images : vos images taggées apparaissent dans la liste !
Le point technique !
Une même image peut avoir autant de noms et de tags que vous voulez. Par exemple, l'image officielle Debian 13 est disponible à la fois sous les tags
debian:13,debian:trixieetdebian:latest: c'est la même image, avec le même IMAGE ID.REPOSITORY TAG IMAGE ID debian 13 3a1b2c3d4e5f debian trixie 3a1b2c3d4e5f debian latest 3a1b2c3d4e5fAttention :
latestdésigne simplement la dernière version au moment où vous l'avez récupérée. Lors de la prochaine version de Debian,debian:latestpointera vers une autre image. Pour un projet, préférez toujours un tag explicite (debian:13,postgres:17…).
Gestion des conteneurs
Maintenant que la gestion des images n'est plus un mystère pour vous, parlons un peu des conteneurs.
Définir le fonctionnement d'un conteneur
Un conteneur incarne la notion d'environnement : c'est une instance vivante d'une image.
L'image est statique : on n'écrit jamais dedans. On crée un conteneur à partir d'une image, et ce conteneur stocke ses propres modifications dans sa couche inscriptible. Vous suivez ?
Un conteneur stocke aussi ses paramètres d'exécution : ports publiés, volumes montés, variables d'environnement, réseau…
Le point technique !
Certains paramètres peuvent être modifiés à chaud avec
docker update: les limites de CPU et de mémoire, ou la politique de redémarrage (--restart).En revanche, les ports, les volumes ou les variables d'environnement ne se modifient pas sur un conteneur existant. La bonne pratique est simple : on supprime le conteneur et on en recrée un avec les nouveaux paramètres. C'est sans risque, à condition que les données importantes soient dans des volumes (nous y venons). C'est d'ailleurs exactement ce que fait Docker Compose quand vous modifiez votre fichier
compose.yaml.
Faisons ensemble un exercice.
Lancer un conteneur
Nous avons une image debian sur notre machine. Lançons un conteneur :
$ docker run debian:13
Et voilà, vous avez exécuté un conteneur à partir d'une image Docker ! Ne vous attendez pas à quelque chose de spectaculaire, mais c'est un début. Si vous avez l'impression que cette commande n'a rien fait, docker ps -l (le dernier conteneur créé) vous montre que des choses se sont passées :
$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9b4841d73b6e debian:13 "bash" 3 seconds ago Exited (0) 3 seconds ago furious_hopper
Un conteneur nommé furious_hopper, identifié par l'ID 9b4841d73b6e, a lancé la commande bash à partir de l'image debian:13, puis s'est terminé avec le code retour 0. Sans terminal attaché, bash n'avait rien à faire et s'est arrêté aussitôt. (Notez que Docker sait nommer ses conteneurs de façon originale !)
Même arrêté (statut Exited), le conteneur n'est pas supprimé, il est conservé (c'est important pour la suite). Pour afficher tous les conteneurs, en cours et arrêtés, utilisez docker ps -a.
Maintenant, nous voulons interagir avec notre conteneur. Pour cela, nous utilisons deux options de docker run :
$ docker run -it debian:13
Vous voilà dans un magnifique conteneur, avec un bash prêt à répondre. Vous remarquerez que le démarrage est quasi instantané. Détaillons :
-i(--interactive) garde l'entrée standard ouverte : ce que vous tapez est bien transmis aubashdu conteneur ;-t(--tty) alloue un pseudo-terminal, pour avoir un vrai prompt, la couleur, etc.
On les combine presque toujours en -it. Et pour un conteneur jetable que vous ne voulez pas garder, ajoutez --rm : il sera supprimé automatiquement à sa sortie.
$ docker run -it --rm debian:13
La commande lancée a été bash, et ce n'est pas un hasard : dans un Dockerfile, l'instruction CMD définit la commande lancée par défaut si aucune n'est précisée. Pour l'image Debian, c'est bash.
Vous pouvez lancer une autre commande en l'ajoutant après le nom de l'image :
$ docker run --rm debian:13 echo "Docker is fun"
Docker is fun
Le conteneur a été créé, a exécuté echo "Docker is fun", puis s'est arrêté : sa commande principale était terminée.
Les commandes du quotidien
Quelques options et commandes que vous utiliserez tout le temps :
# Lancer un conteneur en arrière-plan (-d), avec un nom, en publiant le port 80 du conteneur sur le port 8080 de la machine
$ docker run -d --name mon-site -p 8080:80 mon-nginx:1.0
# Voir les logs d'un conteneur (-f pour les suivre en direct)
$ docker logs -f mon-site
# Ouvrir un shell dans un conteneur qui tourne déjà
$ docker exec -it mon-site bash
# Arrêter, relancer, supprimer
$ docker stop mon-site
$ docker start mon-site
$ docker rm mon-site
Conserver ses données : les volumes
Prenons un exemple concret : mon projet Symfony utilise une base de données. Les données vont évoluer, et je ne veux évidemment pas les perdre (enfin, j'imagine ;) !).
Si je laisse la base de données écrire dans la couche inscriptible du conteneur, mes données disparaissent dès que le conteneur est supprimé. Or nous venons de voir qu'on supprime et recrée régulièrement des conteneurs : pour changer un paramètre, mettre à jour l'image…
La solution, ce sont les volumes : des espaces de stockage gérés par Docker, indépendants du cycle de vie des conteneurs.
$ docker run -d \
--name ma-bdd \
-e POSTGRES_PASSWORD=motdepasse \
-v pgdata:/var/lib/postgresql/data \
postgres:17
Ici, l'option -v pgdata:/var/lib/postgresql/data monte le volume nommé pgdata (créé automatiquement s'il n'existe pas) à l'endroit où PostgreSQL stocke ses données.
Vous pouvez maintenant supprimer le conteneur ma-bdd, en recréer un avec une autre version de PostgreSQL ou d'autres paramètres, et monter à nouveau le volume pgdata : vos données sont toujours là. Le conteneur devient jetable, les données restent. Pour lister vos volumes : docker volume ls.
Le point technique !
On peut aussi monter un dossier de votre machine dans un conteneur (bind mount), par exemple
-v ./src:/var/www/html. C'est très pratique en développement : vous modifiez votre code dans votre éditeur et le conteneur voit les changements immédiatement.
Sous le capot de docker run : create et start
docker run est la commande que l'on utilise le plus, mais elle enchaîne en réalité deux commandes, auxquelles vous avez aussi accès : docker create puis docker start.
docker create
docker create accepte les mêmes options que docker run. Elle crée un conteneur à partir d'une image avec ses paramètres d'exécution (ports, volumes, nom…), sans le démarrer.
Parmi ces paramètres, le nom. Docker en donne toujours un par défaut (furious_hopper, vous vous souvenez ?), mais il est bien plus simple de retrouver le conteneur de votre projet avec un nom choisi plutôt qu'avec « condescending_wozniak » ! XD
$ docker create -it --name wonderful_mobydock debian:13
La commande retourne une longue suite de caractères : l'identifiant complet de votre conteneur. Un docker ps -a vous le montre, au statut Created.
docker start
Le conteneur est créé et configuré, lançons-le :
$ docker start wonderful_mobydock
Par défaut, docker start ne vous attache pas au conteneur. Ajoutez -a (--attach) pour voir sa sortie, et -i pour interagir avec lui :
$ docker start -ai wonderful_mobydock
Pour l'arrêter proprement, utilisez docker stop (réservez docker kill aux cas où le conteneur ne répond plus). Vous pourrez le relancer plus tard avec docker start : il retrouvera sa couche inscriptible telle que vous l'avez laissée.
Dans la pratique, on utilise rarement create seul : docker run -d --name … fait la même chose en une commande. Mais savoir que run = create + start aide à comprendre le cycle de vie d'un conteneur.
Conclusion
Vous savez maintenant récupérer et construire des images, les tagger, lancer des conteneurs, les nommer, les arrêter et les relancer, et surtout conserver vos données grâce aux volumes. Toutes les commandes et options n'ont pas été détaillées ici, mais vous avez les bonnes bases pour commencer à utiliser Docker.
Prochaine étape : quand votre application compte plusieurs conteneurs (serveur web, PHP, base de données…), décrivez-les ensemble dans un fichier compose.yaml avec Docker Compose, plutôt que de taper vos docker run à la main.
La documentation officielle est votre amie, et Docker évolue vite : tenez-vous au courant ! Dans la partie 3, nous regardons ce qui se cache derrière le client docker : le Docker Engine et son API.