Docker expliqué simplement : de 0 à votre premier conteneur
Images, conteneurs, Compose — les trois concepts que vous devez comprendre pour démarrer avec Docker. Avec du code qui tourne vraiment.
La première fois que j'ai entendu parler de Docker, on m'a expliqué que c'était "comme une VM mais en léger". Cette définition est techniquement inexacte et pratiquement inutile. Voilà ce que j'aurais aimé qu'on me dise à la place.
Docker empaquette votre application avec tout ce dont elle a besoin pour tourner — la bonne version de Node, les bonnes dépendances système, les bonnes variables d'environnement — dans une boîte qu'on appelle un conteneur. Cette boîte tourne identiquement sur votre laptop, sur le serveur de votre collègue, et en production. "Ça marche sur ma machine" devient un non-problème.
Installation en une ligne
Docker Desktop s'installe avec un seul script sur Linux et Mac. Sur Windows, téléchargez le .exe depuis le site officiel.
curl -fsSL https://get.docker.com | shdocker --version
# Docker version 27.x.x
docker compose version
# Docker Compose version v2.x.xSi les deux commandes répondent, vous êtes prêt.
Image vs conteneur : la distinction qui compte
Tout le monde confond les deux au début. Voilà la différence :
Une image, c'est un modèle figé. Une recette de cuisine. Elle décrit l'état du système — quel OS, quels fichiers, quelles dépendances, quelle commande lancer. Elle ne "tourne" pas.
Un conteneur, c'est une image en cours d'exécution. C'est la recette en train d'être cuisinée. Vous pouvez lancer dix conteneurs depuis la même image — ils partagent la même recette mais chacun a son propre état, ses propres processus, ses propres données en mémoire.
Image → conteneur
Classe → instance
Recette → plat
ISO → machine virtuelle démarréeLa commande docker run fait les deux d'un coup : elle prend une image et démarre un conteneur à partir d'elle.
Votre premier conteneur
Testons avec nginx, le serveur web. L'image officielle est sur Docker Hub, le registre public.
docker run -p 8080:80 nginxCe que cette commande fait :
- Cherche l'image
nginxlocalement — elle n'existe pas encore - La télécharge depuis Docker Hub
- Démarre un conteneur
- Connecte le port 8080 de votre machine au port 80 du conteneur
Ouvrez http://localhost:8080. Vous avez un serveur nginx qui tourne, sans avoir installé nginx sur votre machine.
# En arrière-plan avec -d (detached)
docker run -d -p 8080:80 --name mon-nginx nginx
# Voir les conteneurs actifs
docker ps
# Voir les logs
docker logs mon-nginx
# Arrêter
docker stop mon-nginx
# Supprimer
docker rm mon-nginxL'option --name donne un nom au conteneur pour qu'on puisse y référer facilement.
Écrire votre premier Dockerfile
Docker Hub fournit des images génériques (nginx, node, postgres). Pour votre propre application, vous construisez votre propre image avec un Dockerfile.
Un Dockerfile est une liste d'instructions. Docker les exécute dans l'ordre pour construire l'image.
# Image de base — Node 20 sur Alpine Linux (léger, ~50MB)
FROM node:20-alpine
# Dossier de travail dans le conteneur
WORKDIR /app
# Copier les fichiers de dépendances en premier
# (optimisation cache — expliquée plus bas)
COPY package*.json ./
# Installer les dépendances
RUN npm ci --only=production
# Copier le reste du code
COPY . .
# Port qu'expose l'application
EXPOSE 3000
# Commande au démarrage du conteneur
CMD ["node", "server.js"]# Construire l'image (le . désigne le dossier courant)
docker build -t mon-app .
# Lancer un conteneur depuis cette image
docker run -p 3000:3000 mon-appPourquoi copier package.json avant le reste du code ?
Docker construit les images en couches. Chaque instruction COPY ou RUN crée une couche. Docker met ces couches en cache : si une couche n'a pas changé, il la réutilise sans la reconstruire.
package.json change rarement comparé au code source. En le copiant en premier, npm ci est mis en cache et ne retourne que si vous modifiez vos dépendances. Sans cette optimisation, npm ci retourne à chaque docker build, même pour un changement d'une ligne dans votre code.
Variables d'environnement et volumes
Deux mécanismes essentiels avant de passer à Compose.
# Passer des variables d'environnement
docker run -e DATABASE_URL=postgresql://localhost/mydb mon-app
# Monter un dossier local dans le conteneur (développement)
docker run -v $(pwd)/data:/app/data mon-appLe volume -v monte un dossier de votre machine dans le conteneur. Les modifications dans /app/data côté conteneur apparaissent dans ./data côté machine, et vice versa. Idéal en développement pour ne pas reconstruire l'image à chaque changement.
En production, les volumes servent à persister les données d'une base de données entre les redémarrages du conteneur.
Docker Compose : gérer plusieurs services
Une application réelle a rarement un seul service. Elle a une API, une base de données, peut-être un cache Redis. Relancer manuellement docker run pour chaque service avec tous ses paramètres devient vite ingérable.
Docker Compose décrit toute la stack dans un fichier docker-compose.yml.
services:
api:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://user:secret@db:5432/myapp
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:# Démarrer toute la stack
docker compose up -d
# Voir les logs de tous les services
docker compose logs -f
# Arrêter sans supprimer les données
docker compose stop
# Tout arrêter et supprimer les conteneurs
docker compose downNotez db dans l'URL de connexion. Dans un réseau Docker Compose, les services se trouvent par leur nom de service — pas par localhost. L'API contacte la base de données via db:5432, pas localhost:5432.
C'est là que PostgreSQL dans Docker prend tout son sens : un développeur qui rejoint le projet fait docker compose up et a une base fonctionnelle en 30 secondes, sans installer PostgreSQL sur sa machine.
Le fichier .dockerignore
Même principe que .gitignore. Évite de copier des fichiers inutiles dans l'image.
node_modules
.git
.env
*.log
dist
.next
coverageSans ce fichier, COPY . . copie node_modules (plusieurs centaines de MB) dans l'image avant que npm ci les réinstalle. L'image double de taille inutilement.
Ce que vous venez d'apprendre
Voilà les fondamentaux :
- Image : recette figée. Conteneur : image en cours d'exécution.
docker run -p hôte:conteneur image: démarrer un conteneur depuis une image publique.Dockerfile: recette de votre propre image.docker build -t nom .: construire l'image.docker-compose.yml: orchestrer plusieurs services localement.
Pour aller plus loin — multi-stage builds pour réduire la taille des images en production, sécurité (éviter root), intégration avec GitHub Actions pour automatiser le build et le déploiement — l'article Docker avancé : packager une application pour qu'elle tourne partout couvre tout ça.
Docker change la façon de penser le déploiement. Votre application devient un artefact portable. Le serveur n'a besoin que de Docker pour faire tourner n'importe quelle version de votre app.
Quand un seul serveur ne suffit plus — scaling, haute disponibilité, zéro downtime — Kubernetes pour les débutants est l'étape suivante naturelle.