1. Introduction a Docker

débutant

Docker est un outil qui permet d'empaqueter une application et tout ce dont elle a besoin (code, librairies, configuration) dans un conteneur. Ce conteneur tourne de la même maniere sur ton PC, sur le serveur de prod, ou chez n'importe quel hebergeur.

Pourquoi c'est utile ? Parce que le grand classique du dev, c'est "chez moi ca marche". Ton code tourne sur ta machine avec Node 20, mais le serveur a Node 18 et ca casse. Docker regle ca : tu embarques Node 20 dans le conteneur, et il tournera pareil partout.

SANS DOCKER                          AVEC DOCKER

  Ta machine       Serveur             Une image Docker
  Node 20          Node 18              contient Node 20
  Lib X v1         Lib X v2             Lib X v1
  Config A         Config B             Config A
     │                │                     │
     ▼                ▼                     ▼
  "Ca marche"    "Ca casse"            Conteneur identique
                                        partout, tout le temps

Docker vs machine virtuelle

Une machine virtuelle (VM) emule un systeme d'exploitation complet. Docker, lui, partage le noyau de l'OS hote et isole juste les processus. C'est beaucoup plus leger.

Critere Machine virtuelle Conteneur Docker
OS embarque Completement (guest OS) Non, partage le noyau hote
Poids Quelques Go Quelques Mo a quelques centaines de Mo
démarrage Quelques dizaines de secondes Moins d'une seconde
Isolation Forte (OS separe) Process-level (suffisant la plupart du temps)
Overhead Important Faible, quasi natif
Analogie : Une VM, c'est comme construire une maison entiere pour y stocker tes affaires. Un conteneur Docker, c'est comme une boite de transport standardisee : tu mets tes affaires dedans, et la boite va dans n'importe quel camion, bateau ou train sans rien changer.

Ce que Docker n'est pas

  • Pas une VM — pas d'OS complet, pas de démarrage long
  • Pas un orchestrateur — Docker lance des conteneurs, il ne les gere pas a grande echelle (ca, c'est le role de Kubernetes ou Swarm, hors scope de ce tuto)
  • Pas magique — un conteneur mal configure ne tournera pas mieux qu'une app mal configuree

2. Comprendre les concepts fondamentaux

débutant

Trois mots a retenir : image, conteneur, registre.

  REGISTRE (Docker Hub)         IMAGE (modele)         CONTENEUR (instance qui tourne)

  ┌──────────────┐   docker pull   ┌──────────────┐   docker run   ┌──────────────┐
  │  nginx:1.25  │ ──────────────▶ │  nginx:1.25  │ ─────────────▶ │  nginx:1.25  │
  │  alpine:3.19 │                 │  (en local)  │                 │  (en cours)  │
  │  node:20     │                 │              │                 │              │
  └──────────────┘                 └──────────────┘                 └──────────────┘

  Depot distant                    Modele inerte                    Processus vivant
  (telechargeable)                (ne tourne pas)                  (tourne, a un etat)

Image

Une image est un modele inerte. C'est un systeme de fichiers en couches (layers) qui contient le code, les librairies et la configuration. Une image ne tourne pas, elle existe juste.

Conteneur

Un conteneur est une instance vivante d'une image. Quand tu fais docker run nginx, tu crees un conteneur a partir de l'image nginx. Un conteneur a un ID, un nom, un etat (running, stopped), des logs, etc.

Registre

Un registre est un depot d'images. Le plus connu est Docker Hub (hub.docker.com). On y telecharge des images avec docker pull et on y publie les siennes avec docker push.

Dockerfile

Un Dockerfile est un fichier texte qui decrit comment construire une image. C'est la recette : "prends telle image de base, installe ca, copie ce fichier, lance cette commande".

Docker Compose

Docker Compose est un outil qui permet de lancer plusieurs conteneurs ensemble avec un seul fichier YAML. On l'utilise pour les apps multi-services (ex: web + base de donnees).

Retiens ca : image = recette, conteneur = plat cuisine, registre = magasin d'ingredients, Dockerfile = livre de cuisine, Compose = menu complet.

3. Installation de Docker

débutant

L'installation depend de ton systeme. Docker est disponible sur Linux, macOS et Windows.

# Ubuntu / Debian
curl -fsSL https://get.docker.com | sh

# Ajouter ton user au groupe docker (evite sudo a chaque commande)
sudo usermod -aG docker $USER

# Recharger le groupe (ou fermer/rouvrir la session)
newgrp docker

# démarrer le service
sudo systemctl enable --now docker

# Verifier
docker --version
docker run hello-world
# Option 1 : Docker Desktop (interface graphique)
# télécharger sur https://docs.docker.com/desktop/install/mac-install/
# Puis glisser Docker.app dans Applications

# Option 2 : avec Homebrew
brew install --cask docker

# Lancer Docker.app une fois, puis verifier
docker --version
docker run hello-world
# 1. Activer WSL2 (recommande)
#    Parametres > Applications > Fonctionnalites facultatives >
#    Plus de fonctionnalites Windows > Sous-systeme Windows pour Linux

# 2. Installer Docker Desktop
#    télécharger sur https://docs.docker.com/desktop/install/windows-install/

# 3. Pendant l'installation, cocher "Use WSL 2"

# 4. Lancer Docker Desktop, attendre que l'icone soit verte

# 5. Verifier dans PowerShell
docker --version
docker run hello-world
Windows : Docker Desktop utilise WSL2 (Windows Subsystem for Linux). C'est le mode recommande. Si WSL2 n'est pas active, Docker fonctionnera en mode "Hyper-V" plus lent. Active WSL2 des le depart.

Verifier l'installation

docker --version          # Version du client
docker info               # Infos detaillees sur le moteur
docker run hello-world    # Premier conteneur de test

Si docker run hello-world affiche "Hello from Docker!", ton installation fonctionne.

4. Premiers conteneurs

débutant

Anatomie de docker run

docker run --name mon-nginx -p 8080:80 -d nginx:1.25
#          └── nom du conteneur ┘ └ port hote:conteneur ┘ └ detach ┘ └ image:tag

Explication de chaque option :

Option Role
--name mon-nginx Donne un nom lisible au conteneur (sinon Docker genere un nom aleatoire)
-p 8080:80 Mappe le port 8080 de ta machine vers le port 80 du conteneur. Format hote:conteneur
-d Detached : lance en arrière-plan. Sans cette option, le conteneur occupe le terminal
-it Interactif + terminal. Pour un shell, par exemple docker run -it ubuntu bash
--rm Supprime automatiquement le conteneur quand il s'arrete. Utile pour les tests temporaires
-e VAR=valeur Injecte une variable d'environnement dans le conteneur
-v /hote:/conteneur Monte un volume (fichier ou dossier) de la machine hote vers le conteneur

Premier vrai conteneur : Nginx

# Lancer un serveur web Nginx en arrière-plan
docker run --name web -p 8080:80 -d nginx:1.25

# Ouvrir http://localhost:8080 dans ton navigateur
# Tu vois la page d'accueil de Nginx

# Voir les conteneurs en cours
docker ps

# arrêter le conteneur
docker stop web

# Le redémarrer
docker start web

# Le supprimer (il doit être arrete d'abord)
docker rm web
  TA MACHINE (hote)                     CONTENEUR
  ┌──────────────────────────┐         ┌──────────────────────┐
  │  Port 8080 ───────────────┼─────────│ Port 80 (Nginx)     │
  │                          │         │                      │
  │  Navigateur → localhost:8080 ─────▶│  Sert la page HTML   │
  └──────────────────────────┘         └──────────────────────┘

Mode interactif : un shell dans un conteneur

# Lancer un shell bash dans Ubuntu (temporaire, supprime a la sortie)
docker run -it --rm ubuntu:24.04 bash

# Tu es maintenant dans le conteneur. Essaye :
apt update
apt install -y curl
curl http://localhost
exit   # quitte ET supprime le conteneur (--rm)
Astuce : -it combine deux flags : -i (interactive, garde l'entree standard ouverte) et -t (terminal, alloue un pseudo-TTY). Sans -it, tu ne peux pas interagir avec le shell.

5. Manipuler les images Docker

débutant

télécharger et lister

# télécharger une image sans la lancer
docker pull python:3.12-slim

# Lister les images locales
docker images

# Supprimer une image
docker rmi python:3.12-slim

# Supprimer les images inutilisees
docker image prune

# Supprimer toutes les images non utilisees (attention)
docker image prune -a

Tags et versions

Une image a un nom et un tag (version). Le tag par defaut est latest, mais il vaut mieux toujours specifier une version precise.

Image Tag Taille approx. Usage
alpine 3.19 ~7 Mo Base minimale, ideale pour les images legeres
debian bookworm-slim ~80 Mo Base stable, plus de paquets qu'Alpine
ubuntu 24.04 ~80 Mo Base complete, populaire
node 20-alpine ~50 Mo Node.js sur Alpine, leger
node 20-slim ~250 Mo Node.js sur Debian slim, plus compatible
python 3.12-slim ~130 Mo Python leger
nginx 1.25-alpine ~40 Mo Serveur web leger
postgres 16-alpine ~80 Mo Base de donnees
Evite :latest en production. Ce tag pointe vers la derniere version, qui peut changer a tout moment. Toujours fixer une version precise (nginx:1.25, node:20-alpine) pour la reproductibilite.

Images de base courantes

Choisir sa base, c'est le premier choix d'un Dockerfile. Alpine est leger mais peut casser des paquets (musl au lieu de glibc). Debian slim est un bon compromis : compatible, pas trop lourd.

6. créer sa première image avec un Dockerfile

intermédiaire

Un Dockerfile est la recette de ton image. Chaque ligne cree une couche (layer) qui sera mise en cache par Docker.

Exemple : une API Node.js

Dans un dossier de projet, cree un fichier Dockerfile :

FROM node:20-alpine

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000

CMD ["node", "server.js"]

Explication des instructions

Instruction Role
FROM Image de base. Toujours la première instruction
WORKDIR Definit le dossier de travail dans le conteneur (cree s'il n'existe pas)
COPY Copie des fichiers de la machine hote vers l'image
RUN Execute une commande pendant la construction de l'image (installe des paquets, etc.)
ENV Definit une variable d'environnement dans l'image
ARG Variable disponible uniquement pendant le build (pas au runtime)
EXPOSE Documente le port utilise par l'app (ne publie pas le port, c'est juste informatif)
CMD Commande par defaut au lancement du conteneur. Un seul par Dockerfile
ENTRYPOINT Commande fixe. Combine avec CMD pour des arguments par defaut modifiables

Construire l'image

# Construire une image nommee "mon-api" avec le tag "v1"
docker build -t mon-api:v1 .

# Le point final est important : c'est le "contexte de build"
# (le dossier envoye a Docker pour construire l'image)

# Lancer l'image construite
docker run -p 3000:3000 mon-api:v1

.dockerignore

Cree un fichier .dockerignore a cote du Dockerfile pour eviter d'envoyer des fichiers inutiles au build :

node_modules
npm-debug.log
.git
.env
.DS_Store
*.md
test/
Pourquoi .dockerignore ? Le contexte de build entier est envoye au daemon Docker. Si ton dossier contient node_modules (100 Mo+), il sera envoye a chaque build. Le .dockerignore l'exclut et accelere le build.

Cache des layers

Docker met en cache chaque layer. Si tu modifies un fichier, Docker reconstruit seulement les layers a partir du COPY concerne. C'est pour ca qu'on copie package.json avant le code : si le code change mais pas les dependances, le npm ci est pris dans le cache.

  Dockerfile                         Cache
  ─────────────────                  ──────────────────
  FROM node:20-alpine       ──▶      OK (cache)
  WORKDIR /app              ──▶      OK (cache)
  COPY package.json ...     ──▶      OK (cache) ← ne change pas souvent
  RUN npm ci                ──▶      OK (cache) ← ne refait pas npm install
  COPY . .                  ──▶      REBUILD     ← le code a change
  CMD ["node", ...]         ──▶      REBUILD

7. Persistance des donnees avec les volumes

débutant+

Un conteneur est ephemere : quand tu le supprimes, ses fichiers disparaissent. Pour garder des donnees (base de donnees, config, uploads), on utilise les volumes.

  MACHINE HOTE                    CONTENEUR
  ┌──────────────────┐            ┌──────────────────┐
  │  /var/lib/docker │            │  /app/data      │
  │    /volumes/     │            │                  │
  │      mon-volume  │◄───────────│  (monte via -v)  │
  │      /data       │            │                  │
  └──────────────────┘            └──────────────────┘
        Donnees persistantes              Donnees du conteneur
        (survivent a la suppression)

Volume nomme vs bind mount

Type Syntaxe Quand l'utiliser
Volume nomme -v mon-volume:/app/data Docker gere le stockage. Ideale pour les bases de donnees
Bind mount -v /chemin/hote:/app/data Monte un dossier de ta machine. Ideale pour le dev (hot reload)

Exemples

# Volume nomme : Docker stocke les donnees pour toi
docker run -d --name db -v pg-data:/var/lib/postgresql/data postgres:16-alpine

# Bind mount : monte un dossier de ta machine
docker run -d --name web -p 8080:80 -v ./site:/usr/share/nginx/html nginx:1.25

# Lister les volumes
docker volume ls

# Voir les details d'un volume
docker volume inspect pg-data

# Supprimer un volume (les donnees sont perdues !)
docker volume rm pg-data

# Supprimer tous les volumes inutilises
docker volume prune
Attention : docker volume rm supprime definitivement les donnees. Ne jamais faire ca sur un volume de base de donnees en production sans backup.

8. Comprendre les réseaux Docker

intermédiaire

Par defaut, les conteneurs sont isoles. Pour les faire communiquer entre eux, on utilise les réseaux Docker.

Les types de réseaux

Type Description Usage
bridge réseau virtuel sur la machine. DNS interne (les conteneurs se trouvent par nom) Par defaut, ideal pour la plupart des apps
host Le conteneur partage le réseau de la machine hote Performances brutes, pas d'isolation réseau
none Aucun réseau. Le conteneur est completement isole Taches de calcul hors ligne

Exemple : une app qui parle a une base de donnees

# 1. créer un réseau
docker network create mon-réseau

# 2. Lancer une base de donnees sur ce réseau
docker run -d --name db --network mon-réseau \
  -e POSTGRES_PASSWORD=secret \
  postgres:16-alpine

# 3. Lancer une app qui peut joindre "db" par son nom
docker run -d --name api --network mon-réseau \
  -p 3000:3000 \
  -e DB_HOST=db \
  -e DB_PORT=5432 \
  mon-api:v1

# L'API peut se connecter a "db:5432" (DNS interne de Docker)
  réseau "mon-réseau"
  ┌────────────────────────────────────────────┐
  │                                            │
  │   ┌──────────┐         ┌──────────┐         │
  │   │  "db"    │         │  "api"   │         │
  │   │ Postgres │◄────────│  Node    │         │
  │   │  :5432   │  DNS    │  :3000   │         │
  │   └──────────┘  interne└──────────┘         │
  │                                            │
  └────────────────────────────────────────────┘
                    │
                    ▼  -p 3000:3000
              PORT 3000 EXPOSE VERS L'HOTE
Le DNS interne : quand deux conteneurs sont sur le même réseau Docker, ils se trouvent par leur nom. Pas besoin de connaitre l'IP. db dans l'exemple ci-dessus se resout automatiquement vers l'IP du conteneur "db".

9. Variables d'environnement et configuration

intermédiaire

La regle d'or : ne jamais hardcoder une configuration dans l'image. Une variable d'environnement permet de configurer le conteneur au lancement.

Passer une variable au lancement

# Variable unique
docker run -e PORT=3000 mon-api:v1

# Plusieurs variables
docker run -e PORT=3000 -e DB_HOST=db mon-api:v1

# Depuis un fichier .env
docker run --env-file .env mon-api:v1

Dans le Dockerfile

FROM node:20-alpine
ENV NODE_ENV=production
ENV PORT=3000
# Ces valeurs sont des valeurs par defaut, surchargeables au runtime

ARG vs ENV

Critere ARG ENV
Disponible au build Oui Oui
Disponible au runtime Non Oui
Surchargeable au docker run Non Oui
Exemple Version d'un paquet a installer URL de base de donnees
Ne mets jamais de secrets dans un Dockerfile. Les ENV et ARG sont visibles dans l'image avec docker inspect. Utilise les secrets Docker ou passe les variables au runtime via --env-file (et ne committe pas le .env).

10. Docker Compose

intermédiaire

Docker Compose permet de decrire une application multi-conteneurs dans un seul fichier YAML. On lance tout avec une commande. On utilise docker compose (v2, integre a Docker) et non l'ancienne commande docker-compose (v1, separee et obsolete).

Exemple de base

Cree un fichier compose.yaml :

services:
  web:
    image: nginx:1.25-alpine
    ports:
      - "8080:80"
    volumes:
      - ./site:/usr/share/nginx/html

  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DB_HOST=db
      - DB_PORT=5432
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_PASSWORD=secret
    volumes:
      - pg-data:/var/lib/postgresql/data

volumes:
  pg-data:

Commandes Compose

# Lancer tous les services (en arrière-plan)
docker compose up -d

# Lancer et construire les images en même temps
docker compose up -d --build

# Voir les services en cours
docker compose ps

# Voir les logs de tous les services
docker compose logs -f

# Voir les logs d'un service precis
docker compose logs -f api

# exécuter une commande dans un service
docker compose exec api sh

# arrêter et supprimer les conteneurs (volumes conserves)
docker compose down

# arrêter et supprimer aussi les volumes (attention !)
docker compose down -v
docker compose down -v supprime les volumes. Les donnees de la base seront perdues. Ne l'utilise qu'en dev ou si tu sais ce que tu fais.

depends_on

depends_on controle l'ordre de démarrage, mais n'attend pas que le service soit pret. Si l'API demarre avant que Postgres ait fini son init, la connexion echouera. Solution : healthcheck (voir section 13) ou retry cote application.

11. Construire une application multi-conteneurs

intermédiaire

On construit une stack complete : frontend Nginx + API Node.js + PostgreSQL.

Structure du projet

mon-app/
├── compose.yaml
├── frontend/
│   ├── index.html
│   └── Dockerfile
├── api/
│   ├── package.json
│   ├── server.js
│   └── Dockerfile
└── .env

API Node.js

api/server.js :

import express from 'express';

const app = express();
const PORT = process.env.PORT || 3000;
const DB_HOST = process.env.DB_HOST || 'localhost';
const DB_PORT = process.env.DB_PORT || 5432;

app.get('/api/health', (req, res) => {
  res.json({ status: 'ok', db: `${DB_HOST}:${DB_PORT}` });
});

app.listen(PORT, () => console.log(`API sur le port ${PORT}`));

api/Dockerfile :

FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Frontend Nginx

frontend/Dockerfile :

FROM nginx:1.25-alpine
COPY index.html /usr/share/nginx/html/
EXPOSE 80

compose.yaml complet

services:
  frontend:
    build: ./frontend
    ports:
      - "8080:80"
    depends_on:
      - api

  api:
    build: ./api
    ports:
      - "3000:3000"
    environment:
      - DB_HOST=db
      - DB_PORT=5432
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=app
    volumes:
      - pg-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  pg-data:
# Tout lancer
docker compose up -d --build

# Tester
curl http://localhost:3000/api/health
# Ouvrir http://localhost:8080 pour le frontend

# Tout arrêter
docker compose down

12. Multi-stage builds

avancé

Un multi-stage build permet d'utiliser plusieurs images dans un seul Dockerfile. On construit dans une image complete, puis on copie seulement le resultat dans une image leger.

Exemple : app Node.js avec build

# Stage 1 : build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2 : production (image finale, leger)
FROM nginx:1.25-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Le premier stage installe tout et build. Le second stage prend juste le resultat (/app/dist) et le met dans Nginx. L'image finale ne contient pas Node.js, npm, ni les sources : elle est minuscule.

Gain : une image avec build complet fait ~1 Go. La même en multi-stage fait ~50 Mo. On supprime tout ce qui n'est pas necessaire au runtime.

13. Healthchecks et gestion du démarrage

avancé

Un healthcheck permet a Docker de savoir si un conteneur est vraiment pret. Utile pour depends_on : sans healthcheck, l'API demarre avant que la DB soit prete.

Dans un Dockerfile

FROM node:20-alpine
# ... app ...
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1

Dans Compose

services:
  api:
    build: .
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 10s

  frontend:
    build: ./frontend
    depends_on:
      api:
        condition: service_healthy

Les options :

  • test : la commande a exécuter. Exit 0 = sain, autre = malade
  • interval : intervalle entre deux checks
  • timeout : duree max du check avant de le considerer comme echec
  • retries : nombre d'echecs consecutifs avant de marquer unhealthy
  • start_period : temps de grace au démarrage (echecs ignores)

14. Logs, inspection et diagnostic

intermédiaire

# Logs d'un conteneur
docker logs web

# Suivre les logs en temps reel
docker logs -f web

# Les 50 dernieres lignes
docker logs --tail 50 web

# Logs avec timestamp
docker logs -t web

# Logs depuis une date
docker logs --since 2026-01-01T00:00:00 web

# Entrer dans un conteneur en cours
docker exec -it web sh

# Infos detaillees sur un conteneur (IP, config, volumes, etc.)
docker inspect web

# Voir l'utilisation de ressources
docker stats

# Voir les processus dans un conteneur
docker top web

15. sécurité Docker

avancé

Utilisateur non-root

Par defaut, un conteneur tourne en root. C'est inutile et dangereux. Cree un utilisateur dedie :

FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --chown=app:app . .
USER app
CMD ["node", "server.js"]

Ne pas mettre de secrets dans l'image

Danger : un ENV API_KEY=xxx dans un Dockerfile est visible dans l'image finale avec docker inspect ou docker history. Les secrets se passent au runtime (--env-file, secrets Docker, ou gestionnaire de secrets).

Scanner ses images

# Docker Scout (integre a Docker Desktop)
docker scout cves mon-api:v1

# Voir l'historique des layers (detecte les secrets oublies)
docker history mon-api:v1

Limites de ressources

# Limiter la memoire et le CPU
docker run -d --name api --memory=512m --cpus=1 mon-api:v1

16. Optimisation des images

avancé

Regrouper les RUN

Chaque RUN cree un layer. Regrouper les commandes reduit le nombre de layers et la taille finale.

# Mal : 3 layers, 3 images intermediaires
RUN apt update
RUN apt install -y curl
RUN rm -rf /var/lib/apt/lists/*

# Bien : 1 layer, cleanup dans la même commande
RUN apt update && \
    apt install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*

Choisir la bonne base

node:20 fait ~1 Go. node:20-alpine fait ~50 Mo. Pour la prod, preferer Alpine ou Debian slim.

Analyser les layers

# Voir les layers et leur taille
docker history mon-api:v1

# Dive : outil externe pour analyser une image en detail
# https://github.com/wagoodman/dive
dive mon-api:v1

17. développement local avec Docker

intermédiaire

Hot reload avec bind mount

services:
  api:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - ./:/app          # Monte le code source
      - /app/node_modules # Evite que node_modules hote ecrase celui du conteneur
    command: npx nodemon server.js
    environment:
      - NODE_ENV=development

Le code change sur ta machine, nodemon redetecte et relance. Le conteneur reste tourne.

Le volume anonyme /app/node_modules est une astuce classique : il cree un volume "fantome" qui protege le node_modules du conteneur contre l'ecrasement par le bind mount. Sans ca, le node_modules de l'hote (si il existe) remplacerait celui du conteneur.

18. Registres et publication d'images

avancé

Publier sur Docker Hub

# 1. Se connecter a Docker Hub
docker login

# 2. Tagger l'image avec ton nom d'utilisateur
docker tag mon-api:v1 tonuser/mon-api:v1

# 3. Pousser l'image
docker push tonuser/mon-api:v1

# 4. Quelqu'un d'autre peut la télécharger
docker pull tonuser/mon-api:v1

Autres registres

  • GitHub Container Registry (GHCR) : ghcr.io/tonuser/mon-api:v1
  • Registre prive : lancer un registre local avec docker run -d -p 5000:5000 registry:2
  • Cloud : AWS ECR, Google Artifact Registry, Azure ACR

19. Automatisation et CI/CD

avancé

Exemple de pipeline GitHub Actions qui build et push une image a chaque push sur main.

.github/workflows/docker.yml :

name: Build and Push

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Login to GHCR
        run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

      - name: Build and push
        run: |
          docker build -t ghcr.io/${{ github.repository }}:latest .
          docker push ghcr.io/${{ github.repository }}:latest
Principe : a chaque push sur main, GitHub Actions construit l'image et la pousse sur GHCR. Tu peux ensuite la deployer n'importe ou avec docker pull.

20. Nettoyage et maintenance

intermédiaire

Docker accumule beaucoup de donnees : images, conteneurs arretes, volumes, cache de build. Il faut nettoyer regulierement.

# Voir l'espace utilise par Docker
docker system df

# Supprimer les conteneurs arretes
docker container prune

# Supprimer les images inutilisees
docker image prune

# Supprimer les volumes inutilises (attention aux donnees !)
docker volume prune

# Supprimer les réseaux inutilises
docker network prune

# Tout nettoyer d'un coup (conteneurs arretes, images, réseaux, cache)
docker system prune

# Version extrême : supprime aussi les volumes (donnees perdues !)
docker system prune -a --volumes
Attention : docker system prune -a --volumes supprime tout ce qui n'est pas utilise, y compris les volumes de bases de donnees. A n'utiliser qu'en dev, jamais sur un serveur en prod sans avoir backup les volumes importants.

21. Erreurs fréquentes et dépannage

intermédiaire

Port déjà utilise

# Erreur : "port is already allocated"
# Solution : trouver ce qui occupe le port
# Linux/macOS :
lsof -i :8080
# Windows :
netstat -ano | findstr :8080

# Puis soit arrêter le processus, soit changer le port
docker run -p 8081:80 nginx

Permission denied

# Erreur : "permission denied while trying to connect to Docker daemon"
# Solution (Linux) : ajouter ton user au groupe docker
sudo usermod -aG docker $USER
newgrp docker

Conteneur qui s'arrete immediatement

# Voir le code de sortie
docker ps -a

# Lire les logs du conteneur arrete
docker logs mon-conteneur

# Causes fréquentes :
# - CMD mal ecrit (pas de mode daemon)
# - App qui crash au démarrage (verifier les logs)
# - Variable d'environnement manquante

Disque plein

# Voir l'espace utilise
docker system df

# Nettoyer (prudent)
docker system prune

# Nettoyer le cache de build
docker builder prune

"No space left on device" (Docker Desktop)

Sur macOS/Windows, Docker Desktop utilise un disque virtuel qui grossit. On peut le compresser via Docker Desktop > Settings > Resources > Disk image size > "Clean / Purge data".

22. Bonnes pratiques Docker

avancé

  • Fixer une version precise pour chaque image (node:20-alpine, pas node:latest)
  • Utiliser un .dockerignore pour exclure node_modules, .git, .env
  • Copier package.json avant le code pour profiter du cache de layers
  • Regrouper les RUN avec && pour réduire les layers
  • Nettoyer le cache de paquets dans le même RUN (rm -rf /var/lib/apt/lists/*)
  • Tourner en utilisateur non-root (USER app)
  • Ne jamais mettre de secrets dans un Dockerfile
  • Utiliser des healthchecks pour les dependances entre services
  • Limiter les ressources (--memory, --cpus)
  • Faire des multi-stage builds pour réduire la taille finale
  • Utiliser docker compose (v2) et non docker-compose (v1 obsolete)
  • Scanner ses images avec docker scout

23. Aide-memoire des commandes

Conteneurs

Commande Role
docker run IMAGELancer un conteneur
docker psConteneurs en cours
docker ps -aTous les conteneurs (même arretes)
docker stop NOMarrêter un conteneur
docker start NOMdémarrer un conteneur arrete
docker restart NOMredémarrer
docker rm NOMSupprimer un conteneur arrete
docker rm -f NOMSupprimer de force (en cours)
docker logs NOMVoir les logs
docker logs -f NOMSuivre les logs
docker exec -it NOM shEntrer dans un conteneur
docker inspect NOMInfos detaillees
docker statsConsommation en temps reel

Images

Commande Role
docker pull IMAGEtélécharger une image
docker imagesLister les images locales
docker build -t NOM .Construire une image
docker rmi IMAGESupprimer une image
docker tag IMAGE NEWRenommer / tagger
docker push IMAGEPublier sur un registre
docker history IMAGEVoir les layers

Volumes et réseaux

Commande Role
docker volume lsLister les volumes
docker volume create NOMcréer un volume
docker volume rm NOMSupprimer un volume
docker network lsLister les réseaux
docker network create NOMcréer un réseau
docker network rm NOMSupprimer un réseau

Compose

Commande Role
docker compose up -dLancer en arrière-plan
docker compose up --buildReconstruire et lancer
docker compose downarrêter et supprimer
docker compose down -vIdem + supprimer les volumes
docker compose logs -fSuivre les logs
docker compose exec SERVICE shEntrer dans un service
docker compose psServices en cours

Nettoyage

Commande Role
docker system dfEspace utilise
docker system pruneNettoyer le superflu
docker system prune -a --volumesTout nettoyer (dangereux)
docker image pruneImages inutilisees
docker volume pruneVolumes inutilises
docker builder pruneCache de build

24. Exercices pratiques

Exercice 1 : Site statique (débutant)

Cree un dossier avec un index.html, un Dockerfile base sur Nginx, build et lance sur le port 8080.

Exercice 2 : API + Redis (intermédiaire)

Cree une API Node.js qui incremente un compteur dans Redis. Utilise Compose pour lancer les deux services avec un réseau et un volume.

Exercice 3 : Stack complete (avancé)

Construis une stack avec frontend Nginx + API Node + PostgreSQL + Adminer. Ajoute des healthchecks, un multi-stage build pour l'API, un utilisateur non-root, et des limites de ressources.

Exercice 4 : Image optimisee (avancé)

Prends une de tes images existantes et reduis sa taille de 50% minimum en utilisant : base Alpine, multi-stage, regroupement de RUN, cleanup, .dockerignore. Verifie avec docker history.

25. Pour aller plus loin

Sur ce site

Ressources externes

Bravo ! Tu sais maintenant ce qu'est Docker, comment lancer des conteneurs, construire des images, orchestrer des services avec Compose, optimiser et sécuriser tes images, et même automatiser tout ca en CI/CD. C'est un outil puissant, mais l'essentiel est la : un conteneur, c'est juste un processus isole qui tourne pareil partout.
Retour aux tutoriels