← Toutes les ressources
Outils du dev · Tuto

Docker, du premier conteneur à Compose

Lancer un service sans rien installer sur votre machine, écrire un Dockerfile propre, relire celui qu'une IA vous propose, puis faire tourner plusieurs services ensemble. Les commandes sont vérifiées dans la documentation officielle de Docker.

Aller plus loin avecFormation professionnelle à Bordeaux
Mis à jour le 31 octobre 2026Lecture 22 minNiveau débutant

01Ce que Docker règle

Un projet tourne sur votre poste et plante chez le collègue ou sur le serveur. Une version de PHP, de Node ou de Postgres diffère, une bibliothèque manque. Docker emballe l'application avec tout ce dont elle a besoin, et ce paquet se lance de la même façon partout.

Ce paquet qui tourne s'appelle un conteneur. C'est un processus isolé du reste de la machine, avec ses propres fichiers, mais qui partage le noyau du système hôte. Une machine virtuelle, elle, embarque un système complet. Le conteneur démarre donc en quelques secondes et pèse bien moins lourd.

Les conteneurs Docker reposent sur le noyau Linux. Sur Mac et sur Windows, Docker Desktop fait tourner une petite machine virtuelle Linux en arrière-plan, sans que vous ayez à vous en occuper.

02L'image et le conteneur

L'image est un modèle figé : des fichiers et des réglages, en lecture seule. Le conteneur est une image en train de tourner. Une même image peut donner dix conteneurs, comme un moule donne dix pièces identiques. Supprimer un conteneur ne touche pas à l'image.

Pour essayer, lancez un serveur web nginx. Docker télécharge l'image la première fois, puis démarre le conteneur.

Terminal · un premier conteneur
docker run -d -p 8080:80 --name mon-nginx nginx:1.29-alpine

Ouvrez http://localhost:8080 : la page d'accueil de nginx s'affiche. L'option -d lance le conteneur en arrière-plan, -p relie un port de votre machine à un port du conteneur, --name lui donne un nom.

docker ps

Liste les conteneurs en cours d'exécution, avec leur nom, leur image et leurs ports.

docker images

Liste les images téléchargées ou construites sur votre machine, avec leur taille.

docker stop mon-nginx

Arrête le conteneur. Il existe toujours et peut redémarrer avec docker start.

docker rm mon-nginx

Supprime le conteneur arrêté. L'image reste là, prête pour le suivant.

03Les tags et les images légères

Les images publiques viennent en général de Docker Hub. Dans nginx:1.29-alpine, ce qui suit les deux-points est le tag : il désigne une version et une variante de l'image.

Sans tag, Docker prend latest. Ce nom ne garantit rien : c'est juste le tag par défaut, que l'éditeur de l'image déplace quand il veut. Le jour où il passe à une version majeure, votre build change sans prévenir. Écrivez toujours une version.

La plupart des images existent aussi en variante légère. -slim retire ce qui sert rarement, -alpine part d'Alpine Linux, une distribution minuscule. Voici les tailles compressées affichées par Docker Hub le 31 octobre 2026, pour les processeurs Intel et AMD (amd64).

ImageTaille compressée
python:3.12412 Mo
python:3.12-slim43 Mo
python:3.12-alpine18 Mo
node:24410 Mo
node:24-slim81 Mo
node:24-alpine62 Mo

Alpine utilise la bibliothèque C musl à la place de glibc. Certains paquets compilés pour glibc, fréquents en Python, s'y installent mal ou pas du tout. Partez de -slim par défaut et gardez -alpine pour les images que vous savez compatibles, comme nginx.

04Les données et les réglages

Un conteneur est jetable. Tout ce qu'il écrit dans ses propres fichiers disparaît avec lui. Pour une base de données, c'est la catastrophe assurée : les données doivent vivre ailleurs, dans un volume.

Un volume nommé est géré par Docker et survit à la suppression du conteneur. On le crée, puis on le monte à l'endroit où l'application range ses données. Pour Postgres 17, c'est /var/lib/postgresql/data.

Terminal · une base avec un volume
docker volume create pg-data
docker run -d --name db -e POSTGRES_PASSWORD=[mot-de-passe] -v pg-data:/var/lib/postgresql/data postgres:17-alpine

Le bind mount monte un dossier de votre machine dans le conteneur. Une modification de fichier se voit tout de suite, sans reconstruire. C'est l'outil du développement local : la commande suivante sert le dossier courant comme un site statique.

Terminal · servir le dossier courant
docker run --rm -p 8080:80 -v "$PWD":/usr/share/nginx/html:ro nginx:1.29-alpine

Les réglages passent par des variables d'environnement, une par une avec -e DEBUG=false, ou toutes ensemble depuis un fichier avec --env-file .env. La même image sert alors en local et en production, seules les variables changent.

Aucun secret dans l'image. Une valeur posée par ENV dans le Dockerfile reste inscrite dans l'image, lisible par quiconque la récupère. Passez les mots de passe au lancement, et ajoutez le fichier .env aux deux listes d'exclusion, .gitignore et .dockerignore.

05Les commandes de tous les jours

L'option -p se lit de gauche à droite : port de votre machine, puis port du conteneur. -p 8080:80 envoie ce qui arrive sur le port 8080 de l'hôte vers le port 80 du conteneur.

--rm

Supprime le conteneur dès qu'il s'arrête.

QuandPour un essai ou une commande ponctuelle.

-it

Ouvre un terminal interactif dans le conteneur.

Exempledocker run --rm -it python:3.12-slim bash

Dans le conteneur, l'application doit écouter sur 0.0.0.0. Si elle écoute sur localhost, elle ne répond qu'à l'intérieur du conteneur, et le port publié ne mène nulle part.

Sur un serveur, attention aux ports publiés. Par défaut, Docker publie un port sur toutes les adresses de la machine, et ses règles passent avant celles du pare-feu ufw. Un -p 5432:5432 ouvre donc la base à tout internet, même si ufw la bloque. Ne publiez pas le port d'une base, ou limitez-le à la machine avec -p 127.0.0.1:5432:5432. La documentation détaille le cas dans sa page sur Docker et les pare-feu.

Pour savoir ce qui tourne et regarder à l'intérieur :

docker ps -a

Liste aussi les conteneurs arrêtés, avec leur code de sortie. Un conteneur qui a planté y figure encore.

docker logs -f --tail 100 web

Affiche les 100 dernières lignes du journal, puis suit les nouvelles en direct.

QuandPremier réflexe quand un conteneur refuse de démarrer.

docker exec -it web sh

Ouvre un shell dans un conteneur qui tourne. docker exec web env affiche ses variables d'environnement.

docker inspect web

Donne toute la configuration du conteneur en JSON : réseau, volumes, variables, commande lancée.

docker stats

Montre en continu le processeur, la mémoire et le réseau consommés par chaque conteneur.

Docker lit ce que l'application écrit sur sa sortie standard. Une application qui journalise dans un fichier interne laisse docker logs vide : configurez-la pour écrire sur la console.

Pour arrêter et faire le ménage :

docker stop web

Envoie le signal SIGTERM au processus principal, puis le tue avec SIGKILL s'il n'est pas sorti au bout de 10 secondes sous Linux, 30 sous Windows.

docker system df

Montre la place prise par les images, les conteneurs, les volumes et le cache de construction.

docker image prune

Supprime les images sans tag, restes des constructions successives.

docker system prune

Supprime les conteneurs arrêtés, les réseaux inutilisés, les images sans tag et le cache de construction inutilisé. Avec -a, toutes les images qu'aucun conteneur n'utilise.

Les volumes sont épargnés par défaut. L'option --volumes de docker system prune ajoute les volumes anonymes inutilisés. Un volume nommé comme pg-data ne part qu'avec une commande explicite, docker volume rm pg-data.

06Écrire un Dockerfile

Le Dockerfile est la recette de votre image. Voici celui d'une petite application Flask servie par gunicorn. Le fichier requirements.txt liste flask et gunicorn, et app.py définit un objet app.

Dockerfile · une appli Python
FROM python:3.12-slim
WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

RUN useradd --create-home appuser
USER appuser

EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]

Ligne par ligne : FROM choisit l'image de départ, WORKDIR fixe le dossier de travail. On copie d'abord la liste des dépendances, on les installe, puis on copie le code. useradd et USER font tourner l'application sans les droits root. EXPOSE documente le port écouté sans le publier. CMD donne la commande lancée au démarrage du conteneur.

Terminal · construire et lancer
docker build -t mon-appli:1.0 .
docker run -d -p 8000:8000 -e DATABASE_URL=[url-de-la-base] mon-appli:1.0

Retenez la différence de moment. FROM, WORKDIR, COPY, RUN, ENV, ARG et USER agissent pendant la construction de l'image. CMD, ENTRYPOINT et HEALTHCHECK décrivent ce qui se passera quand un conteneur tournera.

07Les couches et le cache

Chaque instruction RUN, COPY ou ADD ajoute une couche à l'image. Au build suivant, Docker réutilise une couche tant que rien n'a changé pour elle. Dès qu'une couche change, toutes celles du dessus sont reconstruites.

D'où l'ordre du Dockerfile précédent : les dépendances, qui changent rarement, avant le code, qui change à chaque modification. Copiez tout le code en premier et le moindre changement d'un fichier relance l'installation complète des dépendances.

docker build --no-cache -t mon-appli .

Reconstruit tout sans utiliser le cache.

QuandPour récupérer les dernières mises à jour de sécurité des paquets.

docker history mon-appli:1.0

Liste les couches de l'image, l'instruction qui a créé chacune et sa taille.

Une couche ne s'efface jamais. Un fichier copié dans une couche puis supprimé dans une suivante reste dans l'image et alourdit son poids. C'est pour cela que la mise à jour des paquets, leur installation et le nettoyage tiennent dans une seule instruction RUN, sur une même couche.

Dockerfile · installer un paquet système
RUN apt-get update && apt-get install -y --no-install-recommends [paquet] \
    && rm -rf /var/lib/apt/lists/*

Le fichier .dockerignore, à la racine du projet, liste ce que COPY . . ne doit pas envoyer. Il protège vos secrets, évite qu'un dossier inutile casse le cache, et empêche de copier des dépendances compilées pour votre système plutôt que pour Linux.

.dockerignore · point de départ
.git
.env
.env.*
node_modules
dist
__pycache__/
*.pyc
.venv/

Quand l'application se compile, le multi-stage sépare la construction de la livraison. Un premier étage installe les outils et compile. Le second ne récupère que le résultat. Ici, un site statique construit avec Node, puis servi par nginx : l'image finale ne contient ni Node ni node_modules.

Dockerfile · site statique en deux étages
FROM node:24-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:1.29-alpine
COPY --from=build /app/dist /usr/share/nginx/html

Une compilation lourde peut réclamer plusieurs gigaoctets de mémoire. Sur un petit serveur, construisez l'image ailleurs, sur votre poste ou dans un service d'intégration continue, et ne faites que la récupérer en production.

08Relire un Dockerfile écrit par une IA

Un assistant IA écrit un Dockerfile qui fonctionne du premier coup, et qui cache souvent deux ou trois défauts. Passez-le à cette grille avant de l'adopter.

  • Un tag précis sur chaque FROM, jamais latest ni rien du tout.
  • Une variante légère, -slim en général.
  • Les dépendances avant le code, pour profiter du cache.
  • Un .dockerignore à côté du Dockerfile.
  • Aucun secret dans ENV, ARG ou un fichier copié.
  • apt-get groupé dans un seul RUN, avec le nettoyage.
  • Un USER non root. Les images officielles python et node tournent en root par défaut.
  • CMD en forme exec, entre crochets, pour que l'application reçoive les signaux d'arrêt.
  • Un seul rôle par conteneur : l'application d'un côté, la base de l'autre.
  • EXPOSE n'ouvre rien. Seul -p, ou ports: dans Compose, publie un port.
  • Un multi-stage dès qu'il y a une étape de compilation.

La forme exec mérite une explication. En forme shell, CMD gunicorn app:app est lancé par /bin/sh -c. Le shell devient le processus principal et ne transmet pas SIGTERM à votre application. docker stop attend alors ses 10 secondes, puis coupe net, sans laisser l'application finir ses requêtes.

La forme exec ne suffit pas toujours. Un programme lancé comme processus principal ignore SIGTERM s'il ne prévoit rien pour le traiter, et certains serveurs de développement sont dans ce cas. L'option --init de docker run, ou init: true dans Compose, ajoute un petit processus qui relaie les signaux et règle le problème. Pour vérifier, chronométrez un docker stop : s'il dure 10 secondes, l'arrêt n'est pas propre.

docker init

Pose les questions sur votre projet et génère un Dockerfile, un .dockerignore, un compose.yaml et un README.Docker.md de départ.

AttentionIl propose d'écraser les fichiers existants, sans retour possible.

09Docker Compose

Une application réelle a souvent plusieurs morceaux : le site, la base, parfois un cache. Plutôt que trois docker run à rallonge, Compose décrit l'ensemble dans un fichier compose.yaml et le lance d'une commande.

compose.yaml · une appli et sa base
services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://app:${POSTGRES_PASSWORD}@db:5432/app
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_DB: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  db-data:

Compose lit ${POSTGRES_PASSWORD} dans le fichier .env placé à côté. Le service db n'a pas de ports: : la base reste invisible de l'extérieur. Si un vieux tutoriel commence par une ligne version:, supprimez-la. Compose l'ignore et affiche un avertissement qui la signale comme obsolète.

Compose crée un réseau nommé <projet>_default, le projet prenant par défaut le nom du dossier. Chaque service y est joignable par son nom : l'application parle à db:5432, sans adresse IP. Entre services, on utilise le port du conteneur. Le port de l'hôte ne sert qu'aux connexions venues de l'extérieur.

La clé expose rend un port accessible aux autres services sans le publier sur la machine. La clé ports, elle, le publie, avec la même mise en garde que -p sur un serveur.

depends_on règle l'ordre de démarrage. Sous sa forme courte, Compose attend seulement que la base soit lancée, pas qu'elle accepte des connexions. Pour attendre qu'elle soit prête, il faut un healthcheck sur la base et la condition service_healthy, comme dans l'exemple. Les trois conditions possibles :

ConditionCompose attend que la dépendance
service_startedsoit démarrée, ce que fait la forme courte
service_healthypasse son healthcheck
service_completed_successfullyse termine sans erreur, utile pour une migration de base

Sans réglage, un healthcheck tourne toutes les 30 secondes, abandonne une vérification après 30 secondes et déclare le service en échec après 3 tentatives ratées. Pour une base qui démarre en quelques secondes, des intervalles de 5 secondes évitent d'attendre une demi-minute.

restart dit quoi faire quand un conteneur s'arrête. no, la valeur par défaut, ne relance jamais. always relance toujours. on-failure relance après une sortie en erreur. unless-stopped relance sauf si vous avez arrêté le service vous-même, ce qui en fait le bon choix pour un serveur.

docker compose up -d

Crée et démarre tous les services en arrière-plan. Relancée après une modification de compose.yaml, elle recrée les services concernés.

docker compose up -d --build

Reconstruit les images avant de démarrer.

QuandAprès une modification du code ou du Dockerfile.

docker compose ps

Liste les services du projet et leur état, healthcheck compris.

docker compose logs -f web

Suit le journal d'un service.

docker compose exec web sh

Ouvre un shell dans le service.

docker compose restart web

Redémarre un service. Les changements de compose.yaml ne sont pas pris en compte : pour eux, c'est up -d.

docker compose config

Affiche le fichier final, variables remplacées. Une faute de syntaxe ou une variable vide s'y repère vite.

docker compose down

Arrête et supprime les conteneurs et le réseau. Les volumes restent.

docker compose down -v

Supprime aussi les volumes nommés déclarés dans le fichier et les volumes anonymes. La base repart de zéro.

10Une vraie appli : Umami

Pour mettre tout cela en pratique, lancez Umami, un outil libre de mesure d'audience, sous licence MIT. Son dépôt fournit un fichier Compose prêt à l'emploi. La dernière version publiée est la 3.4.1, sortie le 23 octobre 2026.

Terminal · Umami en local
mkdir umami && cd umami
curl -O https://raw.githubusercontent.com/umami-software/umami/master/docker-compose.yml
docker compose up -d

Au premier lancement, Docker télécharge les images, puis Umami crée ses tables. Ouvrez http://localhost:3000 et connectez-vous avec le compte créé par défaut, admin et le mot de passe umami. Changez-le aussitôt. Si Docker répond address already in use, un autre programme occupe déjà le port 3000.

Le fichier contient deux services, et vous savez maintenant le lire :

  • umami utilise l'image ghcr.io/umami-software/umami:latest, publie le port 3000 et reçoit ses réglages par DATABASE_URL, APP_SECRET et TWO_FACTOR_ENCRYPTION_KEY. Il attend que la base soit saine, active init: true et restart: always, et se vérifie en appelant /api/heartbeat.
  • db tourne sur postgres:15-alpine, range ses données dans le volume umami-db-data et se vérifie avec pg_isready. Le double dollar de $${POSTGRES_USER} empêche Compose de remplacer la variable, qui est lue dans le conteneur.

Avant de mettre ce fichier sur un serveur ouvert à internet, changez quatre choses.

  • Épinglez la version : ghcr.io/umami-software/umami:3.4.1 au lieu de latest.
  • Générez les deux secrets, APP_SECRET et TWO_FACTOR_ENCRYPTION_KEY, avec openssl rand -hex 32. Le fichier livré contient des valeurs d'exemple.
  • Sortez les mots de passe de la base dans un fichier .env, lu par ${…}.
  • Limitez le port à la machine avec "127.0.0.1:3000:3000", et placez devant un reverse proxy comme Caddy ou nginx, qui gère le HTTPS.

Le piège du mot de passe Postgres. L'image ne lit POSTGRES_PASSWORD qu'au tout premier démarrage, quand le volume est vide. Changer la variable ensuite ne change rien, et l'application se heurte à password authentication failed. Fixez le mot de passe avant le premier lancement, ou changez-le dans la base elle-même.

Pour mesurer un site, ajoutez-le dans Umami, puis copiez le code de suivi qu'il fournit. Il ressemble à ceci :

HTML · code de suivi Umami
<script defer src="https://[votre-domaine-umami]/script.js" data-website-id="[identifiant-du-site]"></script>

Sur un site WordPress, la balise se place dans l'en-tête, par le thème enfant ou une extension d'insertion de code. En production, Umami doit tourner en continu sur un serveur, derrière un nom de domaine en HTTPS : une page en HTTPS refuse de charger un script servi en HTTP. Les bloqueurs de publicité arrêtent une partie des scripts de mesure, alors lisez les chiffres comme un minimum.

Et Kubernetes ? Il répartit des conteneurs sur un groupe de machines, avec bascule automatique et montée en charge. Pour un site, une base et quelques services sur un seul serveur, Compose suffit largement.

11Les pièges

  • Un FROM sans version. Le build change le jour où latest bouge.
  • Un mot de passe dans le Dockerfile. Il reste lisible dans l'image, même supprimé plus loin.
  • Corriger un conteneur à la main avec exec. La correction disparaît au prochain conteneur. Corrigez le Dockerfile.
  • Une base sans volume. Les données partent avec le premier docker rm.
  • Publier le port de la base sur un serveur. Docker passe devant ufw, la base se retrouve sur internet.
  • Une application qui écoute sur localhost. Le port publié ne répond pas, écoutez sur 0.0.0.0.
  • Croire que depends_on attend que la base soit prête. Il faut un healthcheck et service_healthy.
  • Taper down -v par habitude. Les volumes et leurs données partent avec.

12L'antisèche

Vous voulezTapez
Lancer un conteneurdocker run -d -p 8080:80 --name web image:tag
Voir ce qui tournedocker ps, ou docker ps -a avec les arrêtés
Lire le journaldocker logs -f --tail 100 web
Entrer dans un conteneurdocker exec -it web sh
Arrêter et supprimerdocker stop web puis docker rm web
Garder des données-v pg-data:/chemin/des/donnees
Construire une imagedocker build -t mon-appli:1.0 .
Voir ses couchesdocker history mon-appli:1.0
Mesurer la place prisedocker system df
Faire le ménagedocker system prune
Lancer un projet Composedocker compose up -d
Appliquer un changement de codedocker compose up -d --build
Vérifier le fichier Composedocker compose config
Tout arrêter, données gardéesdocker compose down
Démarrer un Dockerfiledocker init, puis la grille de relecture

Toutes les options sont décrites dans la référence officielle de Docker. Pour le fichier Compose, tout est détaillé dans la référence du format Compose, clé par clé.

Aller plus loin

Envie d'un parcours structuré ?

Ces ressources montrent notre façon de travailler. Pour apprendre étape par étape et appliquer à vos propres projets, Flow Buster propose des formations professionnelles à Bordeaux, en présentiel ou à distance.

Formulaire de contact
Devis gratuit · Bordeaux

Demander un devis

Décrivez votre activité et votre besoin. Nous vous répondons avec un prix ferme et un planning sous 48 h.

Vous préférez parler ? 07 62 01 54 84