Checklist de préparation à la production pour microservices Dockerisés (DevOps)
Ce guide est une checklist pragmatique pour préparer des microservices Dockerisés à la production. Il vise les équipes DevOps/Platform et les développeurs qui livrent des services en conteneurs (Docker, orchestrés par Kubernetes, ECS, Nomad, Swarm, etc.). Les exemples utilisent Linux et des commandes réelles. Adaptez-les à votre environnement.
1) Objectifs de “production readiness”
Avant la technique, clarifiez ce que “prêt pour la prod” signifie pour vous :
- Fiabilité : redémarrage automatique, tolérance aux pannes, gestion des dépendances.
- Sécurité : moindre privilège, secrets protégés, image durcie, surface d’attaque réduite.
- Observabilité : logs structurés, métriques, traces, corrélation, alerting.
- Scalabilité : montée en charge horizontale, limites de ressources, backpressure.
- Exploitabilité : déploiements reproductibles, rollback, migrations maîtrisées, runbooks.
- Conformité : audit, traçabilité, rétention, SBOM, vulnérabilités.
2) Dockerfile : construction reproductible, petite, sûre
2.1 Multi-stage build et images minimales
Utilisez des builds multi-étapes pour éviter d’embarquer toolchains et dépendances de build en runtime.
Exemple (Node.js) :
# Étape build
FROM node:20-bookworm AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Étape runtime
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
USER 10001:10001
ENV NODE_ENV=production
CMD ["dist/server.js"]
Points clés :
- Distroless réduit la surface d’attaque (pas de shell, moins d’outils).
- USER non-root (ici
10001) : indispensable en production. - npm ci pour des installs reproductibles.
Vérifiez la taille :
docker build -t monservice:local .
docker images | grep monservice
2.2 .dockerignore
Réduisez le contexte envoyé au daemon Docker (build plus rapide, moins de fuites).
Exemple .dockerignore :
.git
node_modules
dist
*.log
.env
coverage
2.3 Pinning des versions et digest
Évitez latest. Pinner les versions, idéalement via digest :
FROM python:3.12.3-slim@sha256:...
Raison : reproductibilité et réduction des surprises lors des rebuilds.
2.4 Labels OCI et métadonnées
Ajoutez des labels utiles :
LABEL org.opencontainers.image.source="https://git.example.com/monrepo" \
org.opencontainers.image.revision="$GIT_SHA" \
org.opencontainers.image.created="$BUILD_DATE"
Construisez en injectant les args :
docker build \
--build-arg GIT_SHA="$(git rev-parse HEAD)" \
--build-arg BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-t registry.example.com/monservice:$(git rev-parse --short HEAD) .
3) Exécution : moindre privilège, filesystem en lecture seule, capabilities
3.1 Lancer en non-root
Même si votre orchestrateur force un user, faites-le aussi au niveau image (USER).
Vérification :
docker run --rm monservice:local id
3.2 Read-only root filesystem + tmpfs
De nombreux services n’ont pas besoin d’écrire sur /. Testez :
docker run --rm \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
--tmpfs /run:rw,noexec,nosuid,size=16m \
monservice:local
Si votre app échoue, identifiez ce qui écrit et redirigez vers un volume dédié.
3.3 Drop des Linux capabilities
Par défaut, Docker ajoute des capabilities. En production, réduisez :
docker run --rm \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
-p 8080:8080 \
monservice:local
NET_BIND_SERVICEuniquement si vous devez écouter sur un port <1024.- Sinon, écoutez sur 8080/3000/etc.
3.4 Seccomp et AppArmor
Sur hôtes compatibles, utilisez des profils restrictifs. À minima, gardez le profil seccomp par défaut. Pour aller plus loin, créez un profil adapté (selon runtime/orchestrateur).
4) Configuration : 12-factor, variables d’environnement, validation
4.1 Séparer config et code
- Paramètres via variables d’environnement (ou fichiers montés).
- Pas de secrets dans l’image.
- Pas de
.envcommité.
Exemple de lancement :
docker run --rm \
-e PORT=8080 \
-e LOG_LEVEL=info \
-e DATABASE_URL="postgres://user:pass@db:5432/app" \
monservice:local
4.2 Validation au démarrage
Faites échouer le conteneur si la config est invalide. Cela améliore la sécurité et la fiabilité (fail fast). Exemple de contrôle (conceptuel) :
PORTnumériqueDATABASE_URLparseableLOG_LEVELdans une liste
Testez ce comportement :
docker run --rm -e PORT=abc monservice:local
echo $?
Le code de sortie doit être non-zéro.
4.3 Gestion des dépendances externes
- Timeouts explicites (connexion DB, HTTP clients).
- Retries avec backoff (mais pas infinis).
- Circuit breaker si nécessaire.
5) Réseau : ports, TLS, timeouts, DNS
5.1 Exposer uniquement ce qui est nécessaire
Dans le Dockerfile, EXPOSE est informatif, mais gardez une discipline :
- 1 service = 1 port principal (souvent HTTP).
- Ports admin séparés si besoin (metrics, pprof), idéalement protégés.
5.2 Timeouts côté serveur
Un service HTTP doit avoir :
- timeout de lecture/écriture,
- limite de taille de requête,
- keep-alive contrôlé.
Test de charge simple :
wrk -t4 -c100 -d30s http://localhost:8080/healthz
5.3 DNS et résilience
En environnement orchestré, le DNS peut être instable lors de rollouts. Utilisez :
- cache DNS côté client si possible,
- retries raisonnables,
- timeouts courts.
6) Stockage : stateless par défaut, volumes explicites
6.1 Microservice stateless
Idéalement, le conteneur ne stocke rien localement (sauf cache éphémère). Les données persistantes vont dans :
- base de données,
- object storage (S3),
- volumes gérés (si nécessaire).
6.2 Si persistance : volumes nommés
Test local :
docker volume create monservice-data
docker run --rm -v monservice-data:/var/lib/monservice monservice:local
Documentez :
- chemin de montage,
- format,
- stratégie de backup/restore,
- migrations.
7) Healthchecks : liveness, readiness, startup
7.1 HEALTHCHECK Docker
Même si Kubernetes a ses probes, un HEALTHCHECK aide en exécution simple Docker.
Exemple :
HEALTHCHECK --interval=10s --timeout=2s --retries=3 \
CMD node dist/healthcheck.js || exit 1
Ou via HTTP (si curl disponible, ce qui n’est pas le cas en distroless). Alternative : binaire minimal ou endpoint TCP.
7.2 Readiness vs Liveness
- Readiness : “je peux recevoir du trafic” (DB connectée, migrations ok, caches prêts).
- Liveness : “je ne suis pas bloqué” (deadlock, boucle infinie, fuite mémoire).
- Startup : “j’ai besoin de temps au démarrage” (warmup, migrations).
En pratique :
- readiness doit être stricte (sinon vous envoyez du trafic trop tôt),
- liveness doit être plus tolérante (éviter les redémarrages en boucle).
8) Logs : stdout/stderr, structuré, corrélation
8.1 Écrire sur stdout/stderr
En conteneur, évitez les fichiers de logs locaux. Utilisez stdout/stderr :
- stdout : logs applicatifs
- stderr : erreurs
Test :
docker logs -f <container_id>
8.2 Logs JSON et champs standard
Adoptez un format structuré (JSON) avec :
timestamplevelservicerequest_id/trace_idmethod,path,status,duration_mserror(stacktrace)
Cela facilite l’indexation (ELK, Loki, Datadog, etc.).
8.3 Propagation d’un Request ID
- Acceptez
X-Request-Identrant, sinon générez-en un. - Retournez-le en réponse.
- Loggez-le sur chaque ligne pertinente.
9) Métriques et tracing : SLO, RED/USE, OpenTelemetry
9.1 Métriques essentielles (minimum)
Pour un service HTTP :
- Rate : requêtes/s
- Errors : taux d’erreur (4xx/5xx)
- Duration : latence p50/p95/p99
Pour les ressources :
- CPU, mémoire, GC (si applicable)
- file descriptors
- saturation (threads, pool DB)
Test d’un endpoint metrics (ex. Prometheus) :
curl -s http://localhost:8080/metrics | head
9.2 Traces distribuées
Activez OpenTelemetry :
- instrumentation HTTP server/client,
- propagation W3C
traceparent, - export OTLP.
Vérifiez que les spans apparaissent dans votre backend (Jaeger, Tempo, Datadog, etc.) et que les IDs se retrouvent dans les logs.
10) Gestion des erreurs : codes, retries, idempotence
10.1 Contrats HTTP
- 2xx : succès
- 4xx : erreur client (validation, auth)
- 5xx : erreur serveur (dépendance down, bug)
Évitez de renvoyer 200 avec un champ error=true. Les load balancers, alertes et clients s’appuient sur les codes.
10.2 Idempotence
Pour des endpoints critiques (paiement, création), implémentez une clé d’idempotence :
- header
Idempotency-Key - stockage (DB/Redis) pour rejouer sans dupliquer
10.3 Retries côté client
- Retenter uniquement sur erreurs transitoires (timeouts, 502/503/504).
- Backoff exponentiel + jitter.
- Budget de retry (limiter l’amplification).
11) Ressources : limites CPU/mémoire, OOM, GC, threads
11.1 Comprendre les limites en conteneur
Sans limites, un service peut consommer trop et impacter le nœud. Fixez des limites au niveau orchestrateur. En local Docker, vous pouvez simuler :
docker run --rm -m 256m --cpus="0.5" monservice:local
Surveillez :
docker stats
11.2 Comportement en OOM
Testez le cas où la mémoire est insuffisante :
- le process est-il tué ?
- redémarre-t-il proprement ?
- y a-t-il des fuites ?
Pour Java/Node, configurez la mémoire en tenant compte des cgroups (versions récentes gèrent mieux, mais vérifiez).
12) Démarrage/arrêt : signaux, shutdown gracieux, draining
12.1 Gestion de SIGTERM
En production, l’orchestrateur envoie SIGTERM puis SIGKILL après un délai. Votre service doit :
- arrêter d’accepter de nouvelles requêtes,
- terminer les requêtes en cours,
- fermer proprement DB/queues,
- sortir avec code 0.
Test manuel :
docker run --rm --name test-stop -p 8080:8080 monservice:local
# Dans un autre terminal
docker stop -t 20 test-stop
Le -t 20 simule un délai de grâce de 20 secondes.
12.2 PID 1 et init
Si votre process est PID 1, il doit gérer correctement les signaux. Sinon, utilisez tini (ou l’option Docker) :
docker run --rm --init monservice:local
Ou dans Dockerfile (si image non distroless) :
RUN apt-get update && apt-get install -y tini && rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["tini","--"]
CMD ["./monservice"]
13) Sécurité : secrets, scanning, SBOM, supply chain
13.1 Secrets : ne jamais les builder dans l’image
Anti-pattern :
ENV DB_PASSWORD=...dans Dockerfile- copier
.envdans l’image
Préférez :
- secrets manager (Vault, AWS Secrets Manager, GCP Secret Manager),
- variables injectées au runtime,
- fichiers montés (Kubernetes Secrets).
13.2 Scan de vulnérabilités
Scannez images et dépendances.
Avec Trivy :
trivy image --severity HIGH,CRITICAL registry.example.com/monservice:abc123
Scannez aussi le repo :
trivy fs --severity HIGH,CRITICAL .
13.3 SBOM (Software Bill of Materials)
Générez une SBOM pour audit et traçabilité.
Avec Syft :
syft registry.example.com/monservice:abc123 -o spdx-json > sbom.spdx.json
13.4 Signature d’images
Signez vos images (Cosign) :
cosign sign registry.example.com/monservice:abc123
cosign verify registry.example.com/monservice:abc123
14) CI/CD : pipelines, tests, qualité, promotion d’artefacts
14.1 Construire une fois, promouvoir
Principe : l’image construite en CI est la même que celle déployée en prod (promotion par tag/digest), pas de rebuild en prod.
Exemple de tagging :
GIT_SHA="$(git rev-parse --short HEAD)"
docker build -t registry.example.com/monservice:$GIT_SHA .
docker push registry.example.com/monservice:$GIT_SHA
docker tag registry.example.com/monservice:$GIT_SHA registry.example.com/monservice:staging
docker push registry.example.com/monservice:staging
En prod, utilisez le digest :
docker pull registry.example.com/monservice@sha256:...
14.2 Tests indispensables
- Unit tests
- Integration tests (DB, queues)
- Contract tests (API)
- Tests de sécurité (SAST, dépendances)
- Lint Dockerfile (Hadolint)
Hadolint :
hadolint Dockerfile
14.3 Politique de merge
- revue obligatoire
- checks CI obligatoires
- protection de branches
- versioning sémantique si applicable
15) Stratégies de déploiement : rolling, blue/green, canary
15.1 Rolling update
- déployer progressivement
- readiness gates pour ne pas envoyer de trafic trop tôt
- rollback rapide si erreurs
15.2 Blue/Green
- deux environnements complets
- bascule de trafic (LB)
- rollback quasi instantané
15.3 Canary
- 1% puis 5% puis 25%…
- métriques d’erreur/latence comme critères de promotion
- nécessite une bonne observabilité
16) Base de données : migrations, compatibilité, rollback
16.1 Migrations versionnées
Ne lancez pas des migrations “à la main” sans traçabilité. Utilisez un outil (Flyway, Liquibase, Alembic, Prisma, etc.) et versionnez les scripts.
16.2 Déploiement sans downtime
Pratique recommandée :
- changements backward compatible d’abord (ajout de colonnes, tables),
- déployer l’app,
- ensuite seulement supprimer/renommer (phase ultérieure).
16.3 Tests de migration
En CI, exécutez migrations sur une DB éphémère :
docker run --rm -d --name pg -e POSTGRES_PASSWORD=pass -p 5432:5432 postgres:16
# Attendre que la DB soit prête puis lancer migrations (exemple)
psql "postgres://postgres:pass@localhost:5432/postgres" -c "SELECT 1;"
# ... commande de migration ...
docker rm -f pg
17) Files d’attente et traitements async : backpressure, DLQ
Si vous avez Kafka/RabbitMQ/SQS :
- configurez les timeouts et max in-flight
- gérez le poison message (DLQ)
- rendez les handlers idempotents
- exposez métriques : lag, taux de retry, DLQ size
Test basique de connectivité réseau (selon protocole) :
nc -vz broker.example.com 9092
18) Observabilité opérationnelle : dashboards, alertes, runbooks
18.1 Dashboards minimaux
Pour chaque service :
- trafic (RPS)
- erreurs 5xx
- latences p95/p99
- saturation CPU/mémoire
- dépendances (DB latency, error rate)
18.2 Alerting basé sur SLO
Évitez d’alerter sur “CPU > 80%” sans contexte. Préférez :
- taux d’erreur > seuil
- latence p99 > seuil
- burn rate de budget d’erreur
18.3 Runbooks
Pour chaque alerte, documentez :
- symptômes
- causes probables
- commandes de diagnostic
- actions de mitigation
- critères de rollback
Exemples de commandes utiles (selon environnement) :
- vérifier endpoints :
curl -i http://service.example.com/healthz
curl -i http://service.example.com/readyz
- vérifier latence :
curl -o /dev/null -s -w "time_total=%{time_total}\n" http://service.example.com/api
19) Tests de résilience : chaos “light”, pannes simulées
19.1 Simuler une dépendance lente
Si vous contrôlez un proxy (ou en dev), injectez de la latence. Sinon, testez côté client avec timeouts très bas pour vérifier le comportement.
19.2 Couper le réseau (local Docker)
Vous pouvez isoler un conteneur sur un réseau dédié et retirer l’accès (approche simplifiée). Exemple : lancer sur un réseau puis stopper la dépendance, observer retries/backoff.
docker network create testnet
docker run -d --name db --network testnet -e POSTGRES_PASSWORD=pass postgres:16
docker run --rm --network testnet -e DATABASE_URL="postgres://postgres:pass@db:5432/postgres" monservice:local
Stopper la DB et vérifier que le service se dégrade proprement (readiness peut passer à false, mais le process reste stable).
20) Performance : profiling, limites, caches
20.1 Profiling contrôlé
En prod, évitez d’exposer des endpoints de profiling sans protection. Préférez :
- activation temporaire,
- réseau interne,
- auth.
20.2 Caches
- TTLs explicites
- taille max
- stratégie d’invalidation
- métriques hit/miss
21) Conformité et gouvernance : audit, rétention, PII
- Identifiez les données personnelles (PII) et évitez de les logger.
- Masquez les secrets (tokens, mots de passe) dans les logs.
- Définissez la rétention des logs/traces/métriques.
- Ajoutez des audits d’accès si nécessaire.
Test simple : cherchez des patterns de secrets dans vos logs (ex. en staging) et corrigez.
22) Checklist finale (à cocher avant prod)
Image & build
- Dockerfile multi-stage, image runtime minimale
- Pas de
latest, versions pinnées (idéalement digest) -
.dockerignorecorrect - Labels OCI (source, revision, date)
- SBOM générée (
syft ...) - Scan vulnérabilités (
trivy image ...) sans HIGH/CRITICAL non justifiées - Image signée (
cosign sign/verify)
Sécurité runtime
-
USERnon-root - Root filesystem en lecture seule si possible (
--read-only) - Capabilities minimales (
--cap-drop ALL+ exceptions) - Secrets injectés au runtime (pas dans l’image)
- Pas de données sensibles dans les logs
Configuration & comportement
- Validation de config au démarrage (fail fast)
- Timeouts explicites (HTTP clients, DB)
- Retries contrôlés + backoff + jitter
- Idempotence sur endpoints critiques
Santé & cycle de vie
- Endpoints
healthz/readyz(etstartupsi besoin) - Shutdown gracieux sur SIGTERM, délai de grâce testé (
docker stop -t ...) - PID1/signal handling correct (
--initou tini)
Observabilité
- Logs sur stdout/stderr, structurés JSON
- Request ID / Trace ID propagés et loggés
- Métriques RED (rate/errors/duration) + ressources
- Tracing OpenTelemetry opérationnel
- Dashboards et alertes SLO prêtes
- Runbooks écrits pour alertes critiques
Déploiement & données
- Stratégie de déploiement définie (rolling/blue-green/canary)
- Rollback testé
- Migrations DB versionnées, stratégie sans downtime
- Tests d’intégration DB/queues en CI
23) Annexes : commandes utiles de diagnostic Docker
Lister conteneurs :
docker ps
docker ps -a
Inspecter un conteneur :
docker inspect <container_id> | less
Voir l’usage ressources :
docker stats
Entrer dans un conteneur (si shell présent) :
docker exec -it <container_id> sh
Suivre les logs :
docker logs -f <container_id>
Tester la config de sécurité d’exécution (exemple) :
docker run --rm \
--read-only \
--cap-drop ALL \
--security-opt no-new-privileges:true \
monservice:local
Conclusion
La production n’est pas un “environnement” : c’est un ensemble d’exigences (sécurité, fiabilité, observabilité, exploitabilité). Pour des microservices Dockerisés, la différence entre un service “qui tourne” et un service “opérable” se joue dans les détails : signaux, healthchecks, limites de ressources, logs structurés, gestion des secrets, scans, promotion d’artefacts, et discipline de déploiement.
Si vous me donnez votre stack (langage, orchestrateur, registry, observabilité), je peux adapter cette checklist avec des exemples concrets (Dockerfile, commandes CI, probes, métriques, conventions de logs) correspondant exactement à vos outils.