SD
DockerNext.jsNestJSDevOpsMongoDB

Docker et Docker Compose : containerisez votre app Next.js + NestJS

Apprenez à containeriser une application fullstack Next.js et NestJS avec Docker Compose. Dockerfile optimisé, multi-stage build et MongoDB inclus.

AMAlexis Mouchon9 min de lecture

Vous avez bâti une belle application fullstack : un frontend Next.js réactif, un backend NestJS solide, une base MongoDB bien structurée. Tout fonctionne en local. Et puis vient le moment fatidique du déploiement — et là, les "ça marche chez moi" commencent à pleuvoir. C'est exactement pour ça que Docker existe. Dans cet article, je vous montre comment containeriser l'ensemble de la stack Next.js + NestJS + MongoDB avec Docker Compose, proprement et efficacement.

Pourquoi Docker pour une app fullstack ?

Docker garantit que votre application tourne exactement de la même façon en local, en CI/CD et en production. Fini les divergences de version Node, les dépendances manquantes ou les variables d'environnement oubliées.

Les bénéfices concrets pour une stack Next.js / NestJS :

Voici la structure de projet cible pour cet article :

monorepo/
├── frontend/          # Next.js App Router + TypeScript
│   ├── Dockerfile
│   └── ...
├── backend/           # NestJS + GraphQL + Mongoose
│   ├── Dockerfile
│   └── ...
└── docker-compose.yml

Dockerfile pour Next.js — le multi-stage build

Le multi-stage build est indispensable pour Next.js : il permet de produire une image de production ultra-légère (~150 Mo) en séparant l'étape de build de l'étape d'exécution.

# frontend/Dockerfile

# ─── Étape 1 : deps ───────────────────────────────────────────────
FROM node:20-alpine AS deps
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --omit=dev

# ─── Étape 2 : builder ────────────────────────────────────────────
FROM node:20-alpine AS builder
WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Variables injectées au build (publiques uniquement)
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL

RUN npm run build

# ─── Étape 3 : runner (image finale) ──────────────────────────────
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

# Standalone output de Next.js (activer dans next.config.ts)
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

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

Important : activez le mode standalone dans votre next.config.ts pour que Next.js génère un serveur Node autonome sans node_modules :

// next.config.ts
const nextConfig = {
  output: "standalone",
};
export default nextConfig;

Dockerfile pour NestJS — build TypeScript optimisé

Le backend NestJS suit la même logique : on compile TypeScript en JS dans une première étape, puis on exécute uniquement le code compilé dans l'image finale.

# backend/Dockerfile

# ─── Étape 1 : builder ────────────────────────────────────────────
FROM node:20-alpine AS builder
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

COPY . .
RUN npm run build
# → génère dist/ avec les fichiers JS compilés

# ─── Étape 2 : runner ─────────────────────────────────────────────
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

COPY package.json package-lock.json ./
RUN npm ci --omit=dev

COPY --from=builder /app/dist ./dist

EXPOSE 4000
CMD ["node", "dist/main.js"]

Avec cette approche, l'image finale ne contient pas TypeScript, ts-node, ni les devDependencies — ce qui réduit drastiquement la surface d'attaque et le poids de l'image.

docker-compose.yml — orchestrer les trois services

C'est ici que la magie opère : Docker Compose lie les trois services (frontend, backend, MongoDB) dans un réseau interne partagé, avec leurs variables d'environnement et leurs volumes.

# docker-compose.yml

version: "3.9"

services:
  # ── MongoDB ──────────────────────────────────────────────────────
  mongo:
    image: mongo:7
    container_name: mongo
    restart: unless-stopped
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
      MONGO_INITDB_DATABASE: ${MONGO_DB}
    volumes:
      - mongo_data:/data/db
    networks:
      - app-network

  # ── Backend NestJS ────────────────────────────────────────────────
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    container_name: nestjs-backend
    restart: unless-stopped
    environment:
      NODE_ENV: production
      PORT: 4000
      MONGODB_URI: mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongo:27017/${MONGO_DB}?authSource=admin
      JWT_SECRET: ${JWT_SECRET}
    ports:
      - "4000:4000"
    depends_on:
      - mongo
    networks:
      - app-network

  # ── Frontend Next.js ──────────────────────────────────────────────
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
      args:
        NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
    container_name: nextjs-frontend
    restart: unless-stopped
    environment:
      NODE_ENV: production
    ports:
      - "3000:3000"
    depends_on:
      - backend
    networks:
      - app-network

volumes:
  mongo_data:

networks:
  app-network:
    driver: bridge

Quelques points clés de cette configuration :

Variables d'environnement et fichier .env

Ne committez jamais de secrets dans votre docker-compose.yml. Utilisez un fichier .env à la racine du projet (et ajoutez-le à votre .gitignore) :

# .env (à la racine, jamais commité)

# MongoDB
MONGO_USER=alexis
MONGO_PASSWORD=super_secret_password
MONGO_DB=myapp

# Backend NestJS
JWT_SECRET=un_jwt_secret_tres_long_et_aleatoire

# Frontend Next.js (variables publiques injectées au build)
NEXT_PUBLIC_API_URL=http://localhost:4000/graphql

Docker Compose charge automatiquement le fichier .env à la racine. Vous pouvez aussi cibler un fichier spécifique avec --env-file .env.production pour gérer plusieurs environnements.

Commandes utiles au quotidien

Une fois tout configuré, voici les commandes essentielles :

# Construire et démarrer tous les services
docker compose up --build -d

# Voir les logs en temps réel
docker compose logs -f

# Voir les logs d'un seul service
docker compose logs -f backend

# Arrêter et supprimer les conteneurs (les volumes sont conservés)
docker compose down

# Reconstruire uniquement le backend après un changement
docker compose up --build -d backend

# Accéder au shell d'un conteneur (debug)
docker compose exec backend sh

# Vérifier l'état des services
docker compose ps

Astuce : .dockerignore pour des builds plus rapides

Comme .gitignore, le fichier .dockerignore exclut des fichiers du contexte de build Docker. Sans lui, node_modules et .next sont copiés inutilement à chaque build :

# frontend/.dockerignore et backend/.dockerignore
node_modules
.next
dist
.env*
*.log
.git
coverage

Intégration avec GitHub Actions

Si vous avez mis en place une pipeline CI/CD (voir l'article sur GitHub Actions avec Next.js et NestJS), Docker s'y intègre parfaitement. Vous pouvez builder les images et les pousser sur un registry (Docker Hub, GitHub Container Registry, ou un registry privé) depuis votre pipeline :

# Extrait d'un workflow GitHub Actions
- name: Build and push backend image
  uses: docker/build-push-action@v5
  with:
    context: ./backend
    push: true
    tags: ghcr.io/alexis-mouchon/backend:latest

L'image est ensuite pullée et redémarrée sur votre serveur via SSH — un déploiement zero-downtime devient atteignable avec quelques lignes supplémentaires.

Conclusion

Containeriser une application Next.js + NestJS + MongoDB avec Docker et Docker Compose, c'est investir quelques heures de configuration pour gagner des semaines de sérénité côté déploiement. Votre stack tourne de façon identique en local et en prod, vos secrets sont bien isolés, et l'intégration avec une CI/CD est naturelle.

La prochaine étape logique ? Passer à Kubernetes ou Docker Swarm si vous devez scaler horizontalement — mais pour la grande majorité des projets, Docker Compose suffit largement.


Vous avez un projet web et vous cherchez un développeur fullstack capable de livrer une architecture propre, du code au déploiement ? Contactez-moi pour en discuter.