Ce que vous saurez faire à la fin
- Installer Docker et lancer votre premier conteneur
- Écrire un Dockerfile multi-stage optimisé
- Orchestrer plusieurs services avec Docker Compose
- Gérer volumes, réseaux, secrets et health checks
- Déployer en production avec registry et stratégie de mise à jour
Durée : 3 heures. Pré-requis : Linux/Mac/Windows avec WSL2, 10 Go d’espace disque, compte Docker Hub.
Étape 1 — Installer Docker
# Linux (un-liner officiel)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
# Déconnectez-vous puis reconnectez-vous
# macOS
brew install --cask docker
# Ou téléchargez Docker Desktop depuis docker.com
# Windows: Docker Desktop avec WSL2 activé
# Vérification
docker --version
docker compose version
docker run hello-world
Étape 2 — Premier conteneur
# Lancer un conteneur Nginx
docker run -d -p 8080:80 --name web nginx:alpine
# Vérifier
curl http://localhost:8080
docker ps
docker logs web
# Entrer dans le conteneur
docker exec -it web sh
# Arrêter et supprimer
docker stop web
docker rm web
Étape 3 — Premier Dockerfile
- Créez un dossier
mon-app/avec un simpleserver.js:
// server.js
const http = require("http");
http.createServer((req, res) => {
res.writeHead(200);
res.end("Hello from Docker!");
}).listen(3000);
console.log("Server running on 3000");
- Créez
Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY server.js .
EXPOSE 3000
USER node
CMD ["node", "server.js"]
- Build et run :
docker build -t mon-app:1.0 .
docker run -d -p 3000:3000 --name app mon-app:1.0
curl http://localhost:3000
docker logs -f app
Étape 4 — Dockerfile multi-stage optimisé
# syntax=docker/dockerfile:1.7
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci --only=production
FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-alpine AS runtime
ENV NODE_ENV=production
WORKDIR /app
# Utilisateur non-root
RUN addgroup -S app && adduser -S app -G app
USER app
COPY --from=build --chown=app:app /app/dist ./dist
COPY --from=deps --chown=app:app /app/node_modules ./node_modules
COPY --chown=app:app package.json ./
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget --spider -q http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
- Avantages : image finale < 200 MB, sans outils de build, sans racines, avec healthcheck.
Étape 5 — .dockerignore
Évite d’envoyer des fichiers inutiles au daemon Docker.
node_modules
dist
.git
.env
.env.*
*.log
coverage
.vscode
.idea
README.md
Dockerfile*
docker-compose*
.github
Étape 6 — Volumes pour la persistance
# Volume nommé (recommandé en prod)
docker volume create pgdata
docker run -d \
--name pg \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
-p 5432:5432 \
postgres:16
# Bind mount (pour dev)
docker run -v "$(pwd):/app" -w /app node:20 npm test
# Lister, inspecter, supprimer
docker volume ls
docker volume inspect pgdata
docker volume rm pgdata # attention: perd les données
Étape 7 — Docker Compose : stack complet
- Créez
compose.yaml:
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: itsc
POSTGRES_USER: itsc
POSTGRES_PASSWORD: ${DB_PASS:?DB_PASS manquant}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U itsc"]
interval: 5s
retries: 10
redis:
image: redis:7-alpine
command: ["redis-server", "--save", "60", "1"]
volumes:
- redisdata:/data
api:
build: .
environment:
DATABASE_URL: postgres://itsc:${DB_PASS}@db:5432/itsc
REDIS_URL: redis://redis:6379
ports:
- "3000:3000"
depends_on:
db: { condition: service_healthy }
redis: { condition: service_started }
restart: unless-stopped
deploy:
resources:
limits: { cpus: "1.0", memory: "512M" }
volumes:
pgdata:
redisdata:
- Créez
.env:
DB_PASS=un_mot_de_passe_fort
- Lancez :
docker compose up -d
docker compose ps
docker compose logs -f api
docker compose exec db psql -U itsc
docker compose down # stop et retire
docker compose down -v # + retire volumes
Étape 8 — Réseau et DNS interne
- Docker Compose crée automatiquement un réseau
itsc_default. - Dans le conteneur api, les noms db et redis sont résolus par DNS interne.
- Pas besoin d’exposer les ports DB à l’extérieur. Seul api est exposé via le port 3000.
docker network ls
docker network inspect itsc_default
Étape 9 — Secrets et variables d’environnement
services:
api:
secrets:
- db_password
- api_key
environment:
DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
external: true # géré par Docker secret
Dans le code, lire depuis /run/secrets/db_password au lieu de variables d’env. Les secrets ne sont pas dans les logs ni dans l’image.
Étape 10 — Image Go ultra-légère
FROM golang:1.22-alpine AS build
WORKDIR /src
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app -ldflags="-s -w" ./cmd/server
FROM scratch
COPY --from=build /app /app
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
USER 65532:65532
ENTRYPOINT ["/app"]
Image finale : 15 MB au lieu de 900 MB. Aucun OS, aucun shell : surface d’attaque minimale.
Étape 11 — Scan de sécurité
# Trivy (gratuit, très complet)
brew install trivy
trivy image mon-app:1.0
# Docker Scout (intégré Docker Desktop)
docker scout cves mon-app:1.0
docker scout recommendations mon-app:1.0
# Snyk
npm install -g snyk
snyk container test mon-app:1.0
Étape 12 — Push vers un registry
# Docker Hub
docker login
docker tag mon-app:1.0 monuser/mon-app:1.0
docker push monuser/mon-app:1.0
# GitHub Container Registry (recommandé)
echo $GH_TOKEN | docker login ghcr.io -u monuser --password-stdin
docker tag mon-app:1.0 ghcr.io/monorg/mon-app:1.0
docker push ghcr.io/monorg/mon-app:1.0
# Registry privé interne
docker login registry.itsc.sn
docker tag mon-app:1.0 registry.itsc.sn/mon-app:1.0
docker push registry.itsc.sn/mon-app:1.0
Étape 13 — Déploiement en production
# Sur le serveur
docker pull ghcr.io/monorg/mon-app:1.2.3
docker compose pull api
docker compose up -d --no-deps api
# Rolling update avec un script simple
#!/usr/bin/env bash
set -e
docker pull ghcr.io/monorg/mon-app:$1
docker compose up -d --no-deps --scale api=2 api
sleep 15 # laisse temps au health check
docker compose up -d --no-deps --scale api=1 api
Étape 14 — Monitoring et logs
docker stats # CPU/RAM/IO temps réel
docker logs --tail=100 -f api
docker inspect --format='{{json .State}}' api | jq
# Logs centralisés (driver gelf/loki)
docker run -d --log-driver=loki \
--log-opt loki-url="https://loki.itsc.sn/loki/api/v1/push" \
mon-app:1.0
Étape 15 — Nettoyage
# Voir ce qui prend de la place
docker system df
# Nettoyer proprement
docker container prune # conteneurs arrêtés
docker image prune -a # images non utilisées
docker volume prune # volumes orphelins
docker network prune # réseaux orphelins
# Tout d'un coup (attention)
docker system prune -a --volumes
Checklist production
✓ Dockerfile multi-stage, image finale < 300 MB
✓ USER non-root dans le conteneur
✓ HEALTHCHECK défini
✓ .dockerignore présent
✓ Secrets via Docker secrets, jamais en env brut
✓ Compose avec depends_on + healthchecks
✓ Limits CPU/RAM définies
✓ Scan Trivy: 0 vuln High/Critical
✓ Tags avec version sémantique (pas latest en prod)
✓ Registry privé pour images internes