Pourquoi cet article
3h du matin, pager qui sonne : “Production down”. Container OOMKilled, image de 2.8GB qui ne build plus, secrets en clair dans les logs.
6 ans d’expérience Docker résumés en 10 erreurs à ne jamais reproduire.
Erreur #1 : Images énormes (2GB+)
Problème
Dockerfile naïf
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["npm", "start"]
Résultat : image de 1.8GB
Problèmes
- Pull lent (3 min au lieu de 20s)
- Deploy lent
- Coûts storage élevés
- Surface d’attaque large
Solution : multi-stage build
# Stage 1 : Build
FROM node:20-alpine AS builder
WORKDIR /app
# Dependencies en premier (cache Docker)
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Stage 2 : Production
FROM node:20-alpine
WORKDIR /app
# Copier uniquement les fichiers nécessaires
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
# User non-root
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
USER nextjs
EXPOSE 3000
CMD ["node", "dist/index.js"]
Résultat : 180MB (−90%)
Optimisations supplémentaires
# .dockerignore (comme .gitignore)
node_modules
.git
.env
*.md
tests/
.github/
coverage/
Gain total : 1.8GB → 85MB après compression
Erreur #2 : Secrets dans l’image
❌ JAMAIS faire ça
# Secrets hardcodés
ENV DATABASE_URL="postgresql://user:password123@db.prod.com/mydb"
# Ou pire
COPY .env .
Problème : docker history expose tout
$ docker history myapp:latest
...
ENV DATABASE_URL=postgresql://user:password123@...
✅ Solutions
1. BuildKit secrets (build-time)
# syntax=docker/dockerfile:1
FROM node:20-alpine
RUN --mount=type=secret,id=npm_token \
echo "//registry.npmjs.org/:_authToken=$(cat /run/secrets/npm_token)" > .npmrc && \
npm install && \
rm .npmrc
docker build --secret id=npm_token,src=.npm_token -t myapp .
2. Environment variables (runtime)
docker run -e DATABASE_URL="$DATABASE_URL" myapp
3. Docker secrets (Swarm/K8s)
# docker-compose.yml
services:
app:
image: myapp
secrets:
- db_password
secrets:
db_password:
external: true
echo "mypassword" | docker secret create db_password -
Erreur #3 : Tout en root
Problème
FROM node:20
# Runs as root par défaut
CMD ["node", "server.js"]
Risque : si l’app est compromise, attaquant = root dans le container
Solution
FROM node:20-alpine
WORKDIR /app
# Créer user non-root
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
COPY --chown=nextjs:nodejs . .
USER nextjs
CMD ["node", "server.js"]
Vérifier
docker exec mycontainer whoami
# nextjs (pas root)
Erreur #4 : Pas de health check
Problème
Container “running” mais app crashée
$ docker ps
CONTAINER ID STATUS
abc123 Up 2 hours # Mais l'app ne répond plus !
Solution
Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD node healthcheck.js || exit 1
healthcheck.js
const http = require('http');
const options = {"{"}
host: 'localhost',
port: 3000,
path: '/health',
timeout: 2000
{"}"};
const req = http.request(options, (res) => {"{"}
process.exit(res.statusCode === 200 ? 0 : 1);
{"}"});
req.on('error', () => process.exit(1));
req.end();
Docker Compose
services:
app:
healthcheck:
test: ["CMD", "node", "healthcheck.js"]
interval: 30s
timeout: 3s
retries: 3
start_period: 40s
Résultat
$ docker ps
CONTAINER ID STATUS
abc123 Up 2 hours (healthy)
Erreur #5 : Un seul process par layer
❌ Anti-pattern
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN apt-get install -y vim
# 4 layers inutiles
✅ Pattern
RUN apt-get update && apt-get install -y \
curl \
git \
vim \
&& rm -rf /var/lib/apt/lists/*
# 1 seul layer + cleanup
Aussi important : ordre des COPY
# ❌ Mauvais : rebuild à chaque changement
COPY . .
RUN npm install
# ✅ Bon : cache des dependencies
COPY package*.json ./
RUN npm ci
COPY . .
Erreur #6 : Logs perdus
Problème
Logs écrits dans des fichiers → perdus quand container restart
Solution : STDOUT/STDERR
❌ Mauvais
const fs = require('fs');
const logStream = fs.createWriteStream('/var/log/app.log');
logger.add(new transports.File({"{"} filename: '/var/log/app.log' {"}"}));
✅ Bon
// Logs sur stdout (Docker capture automatiquement)
console.log('Server started');
console.error('Error occurred');
// Ou Winston sur stdout
logger.add(new transports.Console());
Centraliser avec un log driver
services:
app:
logging:
driver: "fluentd"
options:
fluentd-address: "localhost:24224"
tag: "docker.app"
Ou envoyer à un service
services:
app:
logging:
driver: "awslogs"
options:
awslogs-region: "eu-west-1"
awslogs-group: "/ecs/myapp"
Erreur #7 : Limites de ressources non configurées
Problème
Container bouffe toute la RAM du host
$ docker stats
CONTAINER CPU % MEM USAGE / LIMIT
myapp 45% 7.8GB / 8GB # Oops
Solution
docker run
docker run \
--memory="512m" \
--memory-swap="512m" \
--cpus="0.5" \
--pids-limit=100 \
myapp
docker-compose.yml
services:
app:
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
Kubernetes
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "250m"
memory: "256Mi"
Erreur #8 : Rebuild complet à chaque fois
Problème
Petit changement → 15 minutes de rebuild
Solution : layer caching stratégique
# Dependencies changent rarement
COPY package*.json ./
RUN npm ci
# Source code change souvent
COPY src/ ./src/
# Config change parfois
COPY config/ ./config/
BuildKit cache mounts
# syntax=docker/dockerfile:1
RUN --mount=type=cache,target=/root/.npm \
npm ci
Registry cache
docker build \
--cache-from myregistry.com/myapp:latest \
--tag myregistry.com/myapp:new \
.
Erreur #9 : Ignorer la sécurité
Scans de vulnérabilités
# Trivy (gratuit, excellent)
trivy image myapp:latest
# Ou
docker scan myapp:latest
CI/CD
# .github/workflows/docker.yml
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:${"$"}{"{"}"{"{"} github.sha {"}"}"}"}
format: 'sarif'
severity: 'CRITICAL,HIGH'
Base images officielles minimales
# ❌ Image complète (vulnérabilités)
FROM ubuntu:latest
# ✅ Image minimale
FROM node:20-alpine
# ✅✅ Distroless (encore mieux)
FROM gcr.io/distroless/nodejs20-debian11
Read-only filesystem
# docker-compose.yml
services:
app:
read_only: true
tmpfs:
- /tmp
- /app/.next/cache
Erreur #10 : Pas de monitoring
Metrics essentielles
Prometheus + cAdvisor
# docker-compose.yml
services:
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
ports:
- 8080:8080
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- 9090:9090
prometheus.yml
scrape_configs:
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
Grafana dashboards
- Container CPU usage
- Memory usage
- Network I/O
- Disk I/O
Docker Compose pour production
❌ Ne PAS utiliser docker-compose en prod
✅ Utiliser
- Docker Swarm : simple, intégré
- Kubernetes : complexe mais puissant
- Nomad : alternative à K8s
- ECS/Fargate : si AWS
Exemple Swarm
# docker-stack.yml
version: '3.8'
services:
app:
image: myregistry.com/myapp:latest
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
restart_policy:
condition: on-failure
max_attempts: 3
resources:
limits:
cpus: '0.5'
memory: 512M
networks:
- app-network
secrets:
- db_password
networks:
app-network:
driver: overlay
secrets:
db_password:
external: true
docker stack deploy -c docker-stack.yml myapp
Checklist production
Images
[ ] Multi-stage build
[ ] Alpine ou Distroless
[ ] < 500MB
[ ] .dockerignore configuré
[ ] Scan de vulnérabilités
Sécurité
[ ] User non-root
[ ] Read-only filesystem si possible
[ ] Secrets via environment ou secrets
[ ] Base image à jour
Runtime
[ ] Health checks configurés
[ ] Resource limits
[ ] Logging vers stdout
[ ] Restart policy
Monitoring
[ ] Prometheus + Grafana
[ ] Alertes configurées
[ ] Logs centralisés (ELK, Datadog, etc.)
CI/CD
[ ] Build automatique
[ ] Tests dans container
[ ] Tag sémantique
[ ] Registry privé
Outils recommandés
Développement
- Dive : analyser les layers Docker
- Hadolint : lint Dockerfiles
- Docker Slim : réduire taille images
Production
- Trivy : scan vulnérabilités
- Watchtower : auto-update containers
- Portainer : GUI Docker
- Traefik : reverse proxy
Monitoring
- Prometheus + Grafana
- Datadog
- New Relic
Questions sur Docker en production ? Contactez-moi pour un audit de votre infrastructure.