Si vous êtes dev frontend et que vous n'avez jamais utilisé Docker, vous avez probablement déjà entendu « ça marche sur ma machine ». Docker résout exactement cela — il empaquette votre application avec toutes ses dépendances dans un container qui tourne de la même façon partout : sur votre Mac, sur le PC du collègue, dans le CI et en production. Dans ce guide, je vais de zéro au deploy en me concentrant sur des projets frontend réels.
Qu'est-ce que Docker (sans blabla)
Docker est un outil qui crée des environnements isolés (containers) pour exécuter des applications. Contrairement à une machine virtuelle, un container est léger — il partage le kernel du système et démarre en quelques secondes.
- Image — le « moule ». Elle contient le système d'exploitation de base, Node.js, vos dépendances et votre code.
- Container — l'« instance » de l'image en cours d'exécution. Vous pouvez avoir plusieurs containers de la même image.
- Dockerfile — la « recette » qui définit comment construire l'image.
- docker-compose — orchestre plusieurs containers (app + base de données + redis) en une seule commande.
Installation
Installez Docker Desktop pour votre système d'exploitation. Il inclut Docker Engine, Docker CLI et Docker Compose :
- macOS : téléchargez sur docker.com/products/docker-desktop ou utilisez
brew install --cask docker - Windows : téléchargez Docker Desktop (nécessite WSL2)
- Linux : installez via apt/yum ou suivez la documentation officielle
Vérifiez l'installation :
docker --version
# Docker version 24.x.x
docker compose version
# Docker Compose version v2.x.xPremier Dockerfile : application Next.js
Créons un Dockerfile pour une application Next.js. Le plus basique possible pour comprendre le concept :
# Dockerfile
FROM node:20-alpine
WORKDIR /app
# Copier les dépendances d'abord (cache de layers)
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
# Copier le code
COPY . .
# Build
RUN pnpm build
# Exécuter
EXPOSE 3000
CMD ["pnpm", "start"]Chaque ligne est une layer. Docker met en cache chaque layer — si le package.json n'a pas changé, il saute l'installation des dépendances. C'est pour cela que l'on copie les dépendances avant le code.
# Construire l'image
docker build -t mon-app .
# Exécuter le container
docker run -p 3000:3000 mon-app
# Accéder : http://localhost:3000.dockerignore : ce qui n'entre pas dans l'image
Tout comme le .gitignore, le .dockerignore définit ce qui ne doit pas être copié dans l'image. Cela réduit la taille et accélère le build :
# .dockerignore
node_modules
.next
.git
.env
.env.local
README.md
.vscode
.claudeMulti-stage build : image de production optimisée
Le Dockerfile de base fonctionne, mais l'image finale est lourde — elle contient toutes les devDependencies, le code source et les fichiers de build. En production, vous n'avez besoin que de l'output du build. Le multi-stage build résout cela :
# Dockerfile (multi-stage)
# Stage 1 : Installer les dépendances
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
# Stage 2 : Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN corepack enable && pnpm build
# Stage 3 : Production (uniquement le nécessaire)
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# Copier uniquement le nécessaire depuis le build
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]La différence est significative :
- Dockerfile de base : ~1GB (node_modules complet, code source, devDependencies)
- Multi-stage : ~150MB (uniquement Node.js + output du build + fichiers statiques)
Docker Compose : environnement de dev complet
Pour le développement local, docker-compose orchestre plusieurs services. Un scénario réel : app Next.js + PostgreSQL + Redis :
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- '3000:3000'
volumes:
- .:/app
- /app/node_modules
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/myapp
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
db:
image: postgres:16-alpine
ports:
- '5432:5432'
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
cache:
image: redis:7-alpine
ports:
- '6379:6379'
volumes:
pgdata:# Tout démarrer avec une seule commande
docker compose up -d
# Voir les logs
docker compose logs -f app
# Tout arrêter
docker compose down
# Arrêter et nettoyer les volumes (reset de la base de données)
docker compose down -vLe volumes: - .:/app monte votre code local dans le container — les changements dans le code se reflètent immédiatement sans rebuild. Le - /app/node_modules évite que le node_modules local n'écrase celui du container.
Environnement de dev vs production
Il est courant d'avoir deux Dockerfiles ou d'utiliser target dans le compose :
# docker-compose.dev.yml
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- '3000:3000'
volumes:
- .:/app
- /app/node_modules
command: pnpm dev# Dockerfile.dev (simple, sans multi-stage)
FROM node:20-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install
COPY . .
EXPOSE 3000
CMD ["pnpm", "dev"]# Dev
docker compose -f docker-compose.dev.yml up
# Production
docker compose up --buildVariables d'environnement
Ne mettez jamais de secrets dans le Dockerfile. Utilisez des variables d'environnement via .env ou docker-compose :
# .env (ne pas commiter)
DATABASE_URL=postgresql://user:pass@db:5432/myapp
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_API_URL=http://localhost:3000/api# docker-compose.yml
services:
app:
env_file:
- .env
# ou individuellement :
environment:
- NODE_ENV=productionCommandes essentielles
Les commandes que j'utilise au quotidien :
# Images
docker build -t app . # Construire l'image
docker images # Lister les images
docker rmi app # Supprimer l'image
# Containers
docker ps # Containers en cours d'exécution
docker ps -a # Tous (y compris arrêtés)
docker logs -f <container> # Logs en temps réel
docker exec -it <container> sh # Entrer dans le container
docker stop <container> # Arrêter
docker rm <container> # Supprimer
# Compose
docker compose up -d # Démarrer en background
docker compose down # Arrêter
docker compose down -v # Arrêter + nettoyer les volumes
docker compose logs -f # Logs de tous les services
docker compose build --no-cache # Rebuild sans cache
# Nettoyage
docker system prune -a # Supprimer tout ce qui n'est pas utiliséDeploy en production
Une fois l'image multi-stage prête, le deploy consiste à l'envoyer vers un registry et à l'exécuter sur le serveur :
# Build et tag
docker build -t mon-app:latest .
# Push vers Docker Hub (ou ECR, GCR, etc.)
docker tag mon-app:latest utilisateur/mon-app:latest
docker push utilisateur/mon-app:latest
# Sur le serveur (pull et run)
docker pull utilisateur/mon-app:latest
docker run -d -p 3000:3000 --env-file .env utilisateur/mon-app:latestDes services comme Render, Railway et Fly.io détectent le Dockerfile automatiquement et font build + deploy sans configuration supplémentaire.
Erreurs courantes
- node_modules dans l'image + volume local — le volume écrase le node_modules du container. Solution : ajouter
- /app/node_modulesdans volumes. - Image trop lourde — utiliser
node:20au lieu denode:20-alpine. Alpine est ~5x plus petite. - Build lent — vous ne profitez pas du cache de layers. Copiez
package.jsonavant le code. - Port non accessible — vous avez oublié l'
EXPOSEou le mapping de port dansdocker run -p. - Les variables d'environnement n'arrivent pas — dans Next.js, les variables
NEXT_PUBLIC_*doivent être disponibles au build time, pas seulement au runtime.
Résumé
Docker n'est pas réservé au DevOps — c'est un outil de productivité pour n'importe quel dev. Avec un Dockerfile et un docker-compose, vous garantissez que l'environnement reste cohérent du dev au deploy. L'investissement en apprentissage est faible comparé au retour : moins de bugs d'environnement, un onboarding plus rapide et un deploy prévisible.
