Guide complet7 min de lecture

Optimiser un Dockerfile pour Kubernetes : guide pas à pas

SFEIR Institute

Points clés

  • Les builds multi-étages réduisent les images Docker de 800 MB à 15-30 MB
  • Images Alpine offrent une surface d'attaque réduite pour la production
  • Chaque étape inclut commandes de vérification et sorties attendues

Une image Docker mal optimisée ralentit vos déploiements, consomme des ressources inutiles et crée des vulnérabilités de sécurité. Ce Docker Kubernetes tutorial vous guide à travers chaque étape pour créer des images prêtes pour la production.

TL;DR : Passez d'une image de 800 MB à 15-30 MB grâce aux builds multi-étapes, aux images Alpine, et aux bonnes pratiques de sécurité. Chaque étape inclut les commandes de vérification et les sorties attendues.

Pour maîtriser ces compétences en profondeur, découvrez la formation LFD459 Kubernetes pour les développeurs.


Prérequis

Avant de commencer ce tutoriel, vérifiez que votre environnement est correctement configuré.

Outils requis

# Vérifier Docker
docker --version
# Résultat attendu : Docker version 24.0.0 ou supérieur

# Vérifier kubectl
kubectl version --client
# Résultat attendu : Client Version: v1.29.0 ou supérieur

# Vérifier l'accès au cluster
kubectl cluster-info
# Résultat attendu : Kubernetes control plane is running at https://...

Connaissances préalables

Ce guide suppose que vous maîtrisez les bases de Docker et Kubernetes. Si vous débutez, consultez d'abord notre page sur les différences entre Kubernetes et Docker.

À retenir : 82% des utilisateurs de conteneurs exécutent Kubernetes en production selon le CNCF Annual Survey 2025. Optimiser vos Dockerfiles n'est plus optionnel.

Étape 1 : Choisir une image de base légère

L'image de base détermine 80% de la taille finale de votre conteneur. Une image Alpine pèse environ 3 MB contre 70 MB pour Ubuntu minimale, selon Medium Docker Optimization.

1.1 Comparer les images de base

# Télécharger et comparer les tailles
docker pull alpine:3.19
docker pull ubuntu:22.04
docker pull node:20-alpine
docker pull node:20

docker images --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}"
# Résultat attendu :
# REPOSITORY:TAG          SIZE
# alpine:3.19             7.38MB
# ubuntu:22.04            77.8MB
# node:20-alpine          135MB
# node:20                 1.1GB

1.2 Dockerfile avec image Alpine

Voici un exemple pour une application Node.js :

# ❌ Mauvaise pratique : image complète
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]

# ✅ Bonne pratique : image Alpine
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "server.js"]

Vérification : Comparez les tailles après build :

docker build -t myapp:full -f Dockerfile.full .
docker build -t myapp:alpine -f Dockerfile.alpine .
docker images | grep myapp
# Résultat attendu : myapp:alpine sera 5-10x plus petit

Une image optimisée facilite l'orchestration Kubernetes à grande échelle.


Étape 2 : Implémenter les builds multi-étapes

Les builds multi-étapes sont la technique la plus efficace pour réduire la taille des images. Selon Cloud Native Now, cette approche permet de réduire une image de 800 MB à 15-30 MB.

2.1 Structure d'un build multi-étapes

# Étape 1 : Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Étape 2 : Production
FROM node:20-alpine AS production
WORKDIR /app
ENV NODE_ENV=production

# Copier uniquement les artefacts nécessaires
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./

EXPOSE 3000
CMD ["node", "dist/server.js"]

2.2 Exemple Go (réduction maximale)

# Build
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server

# Production avec scratch (image vide)
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]

Vérification :

docker build -t mygo:optimized .
docker images mygo:optimized
# Résultat attendu : SIZE < 15MB pour une application Go simple
À retenir : L'objectif pour les microservices est de maintenir des images sous 200 MB selon DevOpsCube. Les builds multi-étapes rendent cet objectif réalisable même pour des applications complexes.

Pour approfondir les bonnes pratiques de conteneurisation, la formation LFD459 couvre ces techniques en détail.


Étape 3 : Optimiser l'ordre des couches Docker

Docker met en cache chaque couche. Un mauvais ordonnancement invalide le cache inutilement et rallonge vos builds.

3.1 Principe : du moins fréquent au plus fréquent

# ✅ Ordre optimisé
FROM node:20-alpine

# 1. Dépendances système (change rarement)
RUN apk add --no-cache dumb-init

# 2. Dépendances applicatives (change parfois)
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# 3. Code source (change souvent)
COPY . .

# 4. Métadonnées
EXPOSE 3000
CMD ["dumb-init", "node", "server.js"]

3.2 Utiliser .dockerignore

Créez un fichier .dockerignore pour exclure les fichiers inutiles :

node_modules
npm-debug.log
Dockerfile*
.dockerignore
.git
.gitignore
README.md
.env*
coverage
.nyc_output
*.test.js

Vérification :

# Mesurer le temps de build avec cache
time docker build -t myapp:v1 .
# Modifier un fichier source
echo "// comment" >> server.js
time docker build -t myapp:v2 .
# Résultat attendu : v2 réutilise les couches des dépendances

Consultez notre aide-mémoire Docker et Kubernetes pour les commandes de diagnostic de cache.


Étape 4 : Configurer la sécurité du conteneur

Un conteneur exécuté en root est une faille de sécurité majeure dans Kubernetes. Cette étape est essentielle pour les bonnes pratiques déploiement Kubernetes.

4.1 Créer un utilisateur non-root

FROM node:20-alpine

# Créer un utilisateur applicatif
RUN addgroup -g 1001 -S appgroup && \
    adduser -u 1001 -S appuser -G appgroup

WORKDIR /app
COPY --chown=appuser:appgroup package*.json ./
RUN npm ci --only=production
COPY --chown=appuser:appgroup . .

# Basculer vers l'utilisateur non-root
USER appuser

EXPOSE 3000
CMD ["node", "server.js"]

4.2 Système de fichiers en lecture seule

# Ajouter après USER
RUN chmod -R 555 /app

Vérification :

docker run --rm myapp:secure whoami
# Résultat attendu : appuser

docker run --rm myapp:secure id
# Résultat attendu : uid=1001(appuser) gid=1001(appgroup)
À retenir : Ne jamais exécuter de conteneurs en root dans Kubernetes. Utilisez runAsNonRoot: true dans vos SecurityContext.

Étape 5 : Ajouter les health checks Kubernetes

Les health checks permettent à Kubernetes de détecter et remplacer automatiquement les conteneurs défaillants.

5.1 Health check Docker natif

FROM node:20-alpine

WORKDIR /app
COPY . .
RUN npm ci --only=production

# Health check Docker
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

EXPOSE 3000
CMD ["node", "server.js"]

5.2 Implémentation côté application

// server.js - endpoint /health
app.get('/health', (req, res) => {
  const healthcheck = {
    uptime: process.uptime(),
    status: 'OK',
    timestamp: Date.now()
  };
  res.status(200).json(healthcheck);
});

// Endpoint /ready pour readinessProbe
app.get('/ready', async (req, res) => {
  try {
    // Vérifier les dépendances (DB, cache, etc.)
    await db.ping();
    res.status(200).send('Ready');
  } catch (error) {
    res.status(503).send('Not Ready');
  }
});

5.3 Configuration Kubernetes correspondante

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      - name: myapp
        image: myapp:v1
        ports:
        - containerPort: 3000
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 3
          periodSeconds: 5

Vérification :

kubectl apply -f deployment.yaml
kubectl get pods -w
# Résultat attendu : Pod passe de 0/1 à 1/1 READY
kubectl describe pod myapp-xxx | grep -A5 "Liveness\|Readiness"

Pour plus de détails sur le dépannage Docker et Kubernetes, consultez notre guide dédié.


Étape 6 : Valider et scanner l'image

Avant le déploiement, validez la conformité et la sécurité de votre image.

6.1 Scanner les vulnérabilités

# Avec Trivy (recommandé)
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
    aquasec/trivy:latest image myapp:v1

# Résultat attendu : liste des CVE avec sévérité
# Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

6.2 Vérifier la taille finale

# Analyser les couches
docker history myapp:v1 --human --format "table {{.CreatedBy}}\t{{.Size}}"

# Inspecter avec dive
docker run --rm -it \
    -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive:latest myapp:v1

6.3 Tester localement avec kind

# Charger l'image dans kind
kind load docker-image myapp:v1

# Déployer et tester
kubectl apply -f deployment.yaml
kubectl port-forward svc/myapp 3000:3000
curl http://localhost:3000/health
# Résultat attendu : {"status":"OK","uptime":...}
À retenir : Intégrez ces vérifications dans votre pipeline CI/CD. Un scan de vulnérabilités doit bloquer le déploiement si des CVE critiques sont détectées.

Dépannage : erreurs fréquentes

Erreur : "exec format error"

L'image a été buildée pour une architecture différente.

# Solution : build multi-architecture
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:v1 .

Erreur : "permission denied" au démarrage

Les fichiers n'appartiennent pas à l'utilisateur non-root.

# Vérifier les permissions
docker run --rm myapp:v1 ls -la /app

# Solution dans Dockerfile
COPY --chown=appuser:appgroup . .

Image trop volumineuse malgré les optimisations

# Identifier les couches problématiques
docker history myapp:v1 --no-trunc

# Solutions courantes :
# 1. Combiner les RUN en une seule instruction
# 2. Nettoyer les caches : npm cache clean --force
# 3. Supprimer les fichiers temporaires dans le même RUN

Pour d'autres cas, consultez notre FAQ Docker et Kubernetes.


Dockerfile complet optimisé

Voici le template final intégrant toutes les bonnes pratiques :

# syntax=docker/dockerfile:1.4
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --production

FROM node:20-alpine AS production

# Sécurité : utilisateur non-root
RUN addgroup -g 1001 -S app && adduser -u 1001 -S app -G app
RUN apk add --no-cache dumb-init

WORKDIR /app
COPY --from=builder --chown=app:app /app/dist ./dist
COPY --from=builder --chown=app:app /app/node_modules ./node_modules
COPY --from=builder --chown=app:app /app/package.json ./

USER app
ENV NODE_ENV=production

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget -qO- http://localhost:3000/health || exit 1

EXPOSE 3000
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/server.js"]

Prochaines étapes et formation docker kubernetes

Optimiser un Dockerfile est la première étape vers des déploiements Kubernetes fiables. Pour aller plus loin dans les bonnes pratiques, explorez :

Formations SFEIR pour maîtriser Kubernetes

Contactez nos conseillers pour définir le parcours adapté à vos objectifs.