Les Dockerfiles bien écrits réduisent la taille des images, accélèrent les builds et renforcent la sécurité.
Multi-stage builds
Le pattern principal pour des images fines en production : la première étape compile ou installe les dépendances, la seconde ne conserve que l’artefact final.
# --- Build ---
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
# --- Production ---
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]L’image finale pèse ~180 Mo au lieu de ~1,3 Go.
Ordre des instructions et caching
Docker met en cache chaque couche. Plus une couche dépend de fichiers externes, plus le rebuild est lent.
# ✅ Bon ordre : copier les dépendances AVANT le code source
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# ❌ Mauvais ordre : tout rebuild à chaque modification de code
COPY . .
RUN npm ci --only=production.dockerignore
Exclure ce qui n’est pas nécessaire dans l’image réduit la taille et évite de transmettre des secrets inutiles au daemon.
# .dockerignore
node_modules
.git
target
*.md
.env
DockerfileNe pas mettre .env dans une image de production. Utiliser des variables
d’environnement au runtime (docker run -e, docker-compose.yml, secrets
Kubernetes) à la place.
Sécurité — utilisateur non-root
L’image par défaut s’exécute en root. Un conteneur compromis atteint le daemon Docker. Forcer un utilisateur non-root :
RUN addgroup -S app && adduser -S app -G app
USER appVérifier que l’utilisateur a bien les droits d’écriture sur les répertoires nécessaires (/app, /tmp, les volumes montés).
Exemples par langage
Java / Spring Boot
# Multi-stage Spring Boot
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /build/target/*.jar app.jar
EXPOSE 8080
USER app
ENTRYPOINT ["java", "-jar", "app.jar"]Node.js
# Multi-stage Node.js
FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./
EXPOSE 3000
USER node
CMD ["node", "dist/server.js"]Python
# Multi-stage Python
FROM python:3.12 AS build
WORKDIR /app
COPY requirements.txt .
RUN pip wheel --wheel-dir /wheels -r requirements.txt
COPY src ./src
FROM python:3.12-slim
WORKDIR /app
COPY --from=build /wheels /wheels
RUN pip install --no-index --find-links=/wheels -r requirements.txt
COPY --from=build /app/src ./src
USER 1000
CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0"]Pitfalls courants
| Piège | Correction |
|---|---|
COPY . . avant RUN apt-get : pas de cache si le code change | Copier d’abord les fichiers de deps, construire, puis copier le reste |
RUN rm -rf /var/lib/apt/lists/* dans la même couche que l’install | Combiner dans un seul RUN pour garder le cache |
Multi-stage sans AS nom | Obligatoire : AS builder doit avoir un nom référencé par --from=builder |
ENTRYPOINT en format shell "java -jar app.jar" | Préférer le format exec ["java", "-jar", "app.jar"] (signaux SIGTERM/SIGKILL) |
$HOME dans RUN ne s’applique pas au USER | Vérifier les permissions après le USER avec RUN whoami |
Vérification
# Taille de l'image
docker images mon-app
# Contenu de l'image (utilisateur, ports, environment)
docker inspect mon-app | jq '.[0].Config'
# Scanner de vulnérabilités (Docker Scout)
docker scout cves mon-app
# Vérifier que le conteneur tourne en non-root
docker run --rm mon-app idVoir aussi
- Docker CLI cheatsheet — commandes runtime
- Docker Compose — orchestration multi-containers