Vinicius Aguiar
DevOps

Docker pour les devs frontend : de zéro au deploy

16 avril 2026 · 10 min de lecture

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.x

Premier 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
.claude

Multi-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 -v

Le 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 --build

Variables 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=production

Commandes 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:latest

Des 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_modules dans volumes.
  • Image trop lourde — utiliser node:20 au lieu de node:20-alpine. Alpine est ~5x plus petite.
  • Build lent — vous ne profitez pas du cache de layers. Copiez package.json avant le code.
  • Port non accessible — vous avez oublié l'EXPOSE ou le mapping de port dans docker 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.