Gestion des Dockerfile dans le projet
⚠️ IMPORTANT : il existe différents Dockerfile dans le projet, chacun ayant un but et une resposanbilité différente
A la racine de chaque projet, il existe
- Docker : il s'agit du fichier pour la build en local pour les développeurs. C'est également ceux qui sont utilisés par le Docker Compose. Ce sont les développeurs qui les créés et les maintiennent. Ils peuvent ne pas suivre les pratiques de sécurisation (cf ci-dessous) pour des raisons de praticités permettant le débogage de l'application.
- Docker.Pipeline (pour la build CI) SI BESOIN. Ils sont utilisés par le pipeline pour la CI et le CD. Pour des raisons de logique de CI, ces fichiers Dockerfile peuvent avoir une logique différente (ex : pas de build depuis les sources). Ces Dockerfile doivent être sécurisé et observer les pratiques ci-dessous pour le déploiement sur les environnements. ⚠️ Les développeurs n’y touchent pas (version sécurisée et déployée sur les environnements) ⚠️
Documentation des meilleures pratiques Dockerfile
Introduction
Un Dockerfile est un fichier texte qui contient une série d'instructions utilisées pour construire automatiquement une image Docker. Cette documentation présente les meilleures pratiques pour créer des Dockerfiles efficaces, sécurisés et optimisés.
Utiliser des Images de Base Appropriées
Épingler les versions des images de base :
# ✅ Bon - Version épinglée avec digest
FROM alpine:3.21@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
# ✅ Acceptable - Version épinglée
FROM alpine:3.21
# ❌ Éviter - Version flottante
FROM alpine:latest
Avantages :
- Reproductibilité garantie des builds
- Sécurité renforcée contre les modifications non attendues
- Contrôle précis des mises à jour
Optimisation de la Construction
1. Optimisation du Cache Docker
Ordre optimal des instructions :
FROM node:lts-alpine
WORKDIR /app
# 1. Copier d'abord les fichiers de dépendances
COPY package.json yarn.lock ./
# 2. Installer les dépendances (couche cacheable)
RUN yarn install --production
# 3. Copier le code source en dernier
COPY . .
CMD ["node", "src/index.js"]
Pourquoi cet ordre ?
- Les dépendances changent moins souvent que le code source
- Le cache Docker est invalidé seulement si
package.jsonouyarn.lockchangent - Reconstruction plus rapide lors des modifications de code
2. Réduction du Contexte de Build
Toujours avoir un fichier .dockerignore pour limiter le contexte Docker aux seuls fichiers utiles pour la build.
Fichier .dockerignore :
# .dockerignore
.git
build/
node_modules/
*.log
.env
coverage/
.nyc_output/
Avantages :
- Transfert plus rapide vers le démon Docker
- Réduction de la taille du contexte de build
- Éviter l'inclusion de fichiers sensibles
Builds Multi-étapes
Principe
Les builds multi-étapes permettent de séparer l'environnement de construction de l'environnement d'exécution, réduisant drastiquement la taille de l'image finale.
Exemple pour une Application .NET Core
# =========================================
# Étape 1: Construction de l'application
# =========================================
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /app
# Copier les fichiers projet pour optimiser le cache Docker
COPY *.sln ./
COPY src/MyApp.API/*.csproj ./src/MyApp.API/
COPY src/MyApp.Core/*.csproj ./src/MyApp.Core/
COPY src/MyApp.Infrastructure/*.csproj ./src/MyApp.Infrastructure/
# Restaurer les dépendances (couche cacheable)
RUN dotnet restore
# Copier tout le code source
COPY src/ ./src/
# Construire et publier l'application
RUN dotnet publish src/MyApp.API/MyApp.API.csproj \
-c Release \
-o /app/publish \
--no-restore
# =========================================
# Étape 2: Image de runtime
# =========================================
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
# Créer un utilisateur non-root
RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /app
USER appuser
# Copier uniquement les fichiers publiés depuis l'étape de build
COPY --from=build --chown=appuser:appuser /app/publish ./
# Exposer le port de l'application
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
# Point d'entrée de l'application
ENTRYPOINT ["dotnet", "MyApp.API.dll"]
Exemple pour une Application .NET Core avec Tests
# syntax=docker/dockerfile:1
# =========================================
# Étape 1: Test de l'application
# =========================================
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS test
WORKDIR /app
# Copier les fichiers projet
COPY *.sln ./
COPY src/MyApp.API/*.csproj ./src/MyApp.API/
COPY src/MyApp.Core/*.csproj ./src/MyApp.Core/
COPY src/MyApp.Infrastructure/*.csproj ./src/MyApp.Infrastructure/
COPY tests/MyApp.Tests/*.csproj ./tests/MyApp.Tests/
# Restaurer les dépendances
RUN dotnet restore
# Copier tout le code source
COPY src/ ./src/
COPY tests/ ./tests/
# Exécuter les tests
RUN dotnet test tests/MyApp.Tests/MyApp.Tests.csproj --configuration Release
# =========================================
# Étape 2: Construction de l'application
# =========================================
FROM test AS build
WORKDIR /app
# Publier l'application après les tests
RUN dotnet publish src/MyApp.API/MyApp.API.csproj \
-c Release \
-o /app/publish \
--no-restore
# =========================================
# Étape 3: Image de runtime
# =========================================
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS runtime
WORKDIR /app
# Créer un utilisateur non-root pour la sécurité
RUN addgroup -g 1000 appgroup && \
adduser -u 1000 -G appgroup -h /app -D appuser
# Copier les fichiers publiés
COPY --from=build --chown=appuser:appgroup /app/publish ./
USER appuser
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENTRYPOINT ["dotnet", "MyApp.API.dll"]
Avantages des Builds Multi-étapes
- Taille réduite : Exclusion des outils de build de l'image finale
- Sécurité : Surface d'attaque minimisée
- Performance : Images plus légères, déploiements plus rapides
- Parallélisation : Certaines étapes peuvent s'exécuter en parallèle
Sécurité
1. Utilisateurs Non-Root
Créer et utiliser un utilisateur dédié :
# Créer un utilisateur non-root
RUN groupadd -r appuser && useradd --no-log-init -r -g appuser appuser
# Changer vers l'utilisateur non-root
USER appuser
# Ou utiliser un utilisateur prédéfini
USER nginx
2. Gestion des Secrets
❌ À éviter - Secrets dans ARG/ENV :
# ❌ DANGEREUX - Le secret sera visible dans l'image
ARG AWS_SECRET_ACCESS_KEY
ENV DATABASE_PASSWORD=secret123
✅ Utiliser les Secret Mounts :
# ✅ Sécurisé - Utilisation des secret mounts
RUN --mount=type=secret,id=aws_key \
AWS_ACCESS_KEY_ID=$(cat /run/secrets/aws_key) \
./configure-aws.sh
3. Images de Base Sécurisées
Utiliser des images distroless ou minimales :
# Option 1: Image distroless (très sécurisée)
FROM gcr.io/distroless/base-debian11 AS final
COPY --from=builder /app/myapp /myapp
USER nonroot:nonroot
ENTRYPOINT ["/myapp"]
# Option 2: Alpine (petite et sécurisée)
FROM alpine:3.21 AS final
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/myapp /bin/myapp
USER 1000:1000
ENTRYPOINT ["/bin/myapp"]
Gestion des Dépendances
1. Gestion des Packages Système
Installation optimisée avec apt :
# ✅ Combinaison update + install + nettoyage en une seule couche
RUN apt-get update && apt-get install -y --no-install-recommends \
aufs-tools \
automake \
build-essential \
curl \
libcap-dev \
libsqlite3-dev \
s3cmd=1.1.* \
&& rm -rf /var/lib/apt/lists/*
Bonnes pratiques :
- Combiner
updateetinstalldans la même instruction RUN - Utiliser
--no-install-recommendspour éviter les packages non essentiels - Épingler les versions importantes (
package=version) - Nettoyer le cache apt (
rm -rf /var/lib/apt/lists/*)
2. Téléchargement de Fichiers Distants
Utilisation d'ADD avec validation :
# Téléchargement avec vérification de checksum
FROM scratch AS src
ARG DOTNET_VERSION=8.0.0-preview.6.23329.7
ADD --checksum=sha256:270d731bd08040c6a3228115de1f74b91cf441c584139ff8f8f6503447cebdbb \
https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-runtime-$DOTNET_VERSION-linux-arm64.tar.gz \
/dotnet.tar.gz
Instructions Dockerfile
1. WORKDIR
Utiliser des chemins absolus :
# ✅ Bon - Chemin absolu
WORKDIR /app
# ❌ Éviter - Chemin relatif sans WORKDIR absolu
WORKDIR relative/path
2. CMD vs ENTRYPOINT
Format JSON recommandé :
# ✅ Format JSON (recommandé)
ENTRYPOINT ["./myapp"]
CMD ["--help"]
# ❌ Format shell (peut causer des problèmes de signaux)
ENTRYPOINT ./myapp --help