Kamal 2 : Déployer Next.js sur un VPS avec zéro downtime

AI Bot
Par AI Bot ·

Chargement du lecteur de synthèse vocale...

Introduction

Déployer une application web en production ne devrait pas nécessiter un cluster Kubernetes, un ingénieur DevOps dédié ou une facture cloud à quatre chiffres. Pourtant, en 2026, beaucoup de développeurs se retrouvent piégés entre deux extrêmes : les plateformes managées coûteuses (Vercel, Railway) et la complexité de Kubernetes.

Kamal 2 est la réponse de 37signals (les créateurs de Ruby on Rails et Basecamp) à ce dilemme. Anciennement connu sous le nom de MRSK, Kamal est un outil de déploiement open-source qui permet de déployer des applications conteneurisées sur n'importe quel serveur — VPS, bare metal ou cloud — avec zéro downtime, des rollbacks instantanés et un SSL automatique via Traefik.

Ce qui rend Kamal 2 particulièrement intéressant :

  • Pas de vendor lock-in — fonctionne sur tout serveur avec SSH et Docker
  • Zéro downtime — les nouvelles versions sont lancées avant de couper les anciennes
  • Simple à configurer — un seul fichier YAML (config/deploy.yml)
  • Rollback en une commande — retour à la version précédente instantanément
  • Multi-serveur — déployez sur plusieurs machines en parallèle
  • Accessory support — gérez vos bases de données et services annexes

Dans ce tutoriel, vous allez :

  1. Installer et configurer Kamal 2 sur votre machine locale
  2. Préparer un VPS pour le déploiement
  3. Conteneuriser une application Next.js avec un Dockerfile optimisé
  4. Configurer Kamal pour un déploiement zéro downtime
  5. Mettre en place le SSL automatique avec Traefik
  6. Déployer, surveiller et effectuer des rollbacks

Prérequis

Avant de commencer, assurez-vous de disposer de :

  • Un VPS avec au moins 1 Go de RAM et 1 vCPU (DigitalOcean, Hetzner, OVH, Contabo, etc.)
  • Ubuntu 22.04 ou 24.04 installé sur le VPS
  • Un nom de domaine pointant vers votre serveur (enregistrement DNS A)
  • Docker installé localement (pour construire les images)
  • Ruby 3.1+ installé localement (Kamal est écrit en Ruby)
  • Une application Next.js prête à déployer
  • Un compte Docker Hub (ou un autre registre de conteneurs)
  • Connaissances de base en ligne de commande Linux et Docker

Ce que vous allez construire

À la fin de ce tutoriel, vous aurez une infrastructure de déploiement complète :

  • Un VPS configuré automatiquement par Kamal
  • Une application Next.js en production avec SSL
  • Un proxy Traefik gérant le routage et les certificats
  • Un pipeline de déploiement zéro downtime
  • La capacité de rollback en une seule commande

Étape 1 : Installer Kamal 2

Kamal est distribué comme une gem Ruby. Installez-le globalement sur votre machine de développement :

gem install kamal

Vérifiez que Kamal est correctement installé :

kamal version

Vous devriez voir quelque chose comme 2.4.0 ou une version plus récente.

Si vous préférez ne pas installer Ruby, vous pouvez utiliser Kamal via Docker :

alias kamal='docker run -it --rm -v "${PWD}:/workdir" -v "/run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock" -e SSH_AUTH_SOCK="/run/host-services/ssh-auth.sock" -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/basecamp/kamal:latest'

Étape 2 : Préparer le VPS

2.1 Créer le serveur

Créez un VPS chez votre fournisseur préféré. Pour ce tutoriel, un serveur avec ces spécifications minimales suffit :

RessourceMinimumRecommandé
RAM1 Go2 Go
CPU1 vCPU2 vCPUs
Stockage20 Go SSD40 Go SSD
OSUbuntu 22.04Ubuntu 24.04

2.2 Configurer le DNS

Pointez votre domaine vers le serveur :

Type: A
Nom: app (ou @ pour la racine)
Valeur: <IP_DE_VOTRE_SERVEUR>
TTL: 300

2.3 Configurer SSH

Assurez-vous que vous pouvez vous connecter au serveur via SSH avec votre clé :

ssh root@votre-serveur.com

Kamal se connecte en SSH pour configurer Docker et déployer vos conteneurs. Il gère automatiquement l'installation de Docker sur le serveur lors du premier déploiement — vous n'avez rien à installer manuellement.

Étape 3 : Préparer l'application Next.js

3.1 Créer le projet (si nécessaire)

Si vous partez de zéro :

npx create-next-app@latest mon-app-kamal --typescript --tailwind --app
cd mon-app-kamal

3.2 Créer le Dockerfile

Créez un Dockerfile optimisé pour la production à la racine du projet :

# Étape 1 : Installer les dépendances
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
 
# Étape 2 : Builder l'application
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
 
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
 
RUN npm run build
 
# Étape 3 : Image de production
FROM node:20-alpine AS runner
WORKDIR /app
 
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
 
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
 
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
 
USER nextjs
 
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
 
CMD ["node", "server.js"]

3.3 Configurer Next.js pour le mode standalone

Modifiez votre next.config.js (ou next.config.ts) pour activer le mode standalone :

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
}
 
module.exports = nextConfig

Le mode standalone génère un serveur Node.js autonome qui inclut uniquement les fichiers nécessaires, réduisant considérablement la taille de l'image Docker.

3.4 Ajouter un fichier .dockerignore

Créez un fichier .dockerignore pour accélérer les builds :

node_modules
.next
.git
.gitignore
*.md
docker-compose*.yml
.env*.local

Étape 4 : Initialiser Kamal

4.1 Générer la configuration

Depuis la racine de votre projet Next.js :

kamal init

Cette commande crée les fichiers suivants :

config/
  deploy.yml      # Configuration principale
.kamal/
  secrets         # Variables d'environnement secrètes
.env              # Variables locales (ajouté au .gitignore)

4.2 Configurer deploy.yml

Remplacez le contenu de config/deploy.yml par :

service: mon-app-nextjs
 
image: votre-docker-hub-user/mon-app-nextjs
 
servers:
  web:
    hosts:
      - votre-serveur.com
    labels:
      traefik.http.routers.mon-app-nextjs.rule: Host(`app.votre-domaine.com`)
      traefik.http.routers.mon-app-nextjs.entrypoints: websecure
      traefik.http.routers.mon-app-nextjs.tls.certresolver: letsencrypt
    options:
      network: kamal
 
proxy:
  ssl: true
  host: app.votre-domaine.com
 
registry:
  username: votre-docker-hub-user
  password:
    - KAMAL_REGISTRY_PASSWORD
 
env:
  clear:
    NODE_ENV: production
    HOSTNAME: 0.0.0.0
  secret:
    - DATABASE_URL
 
builder:
  dockerfile: Dockerfile
  multiarch: false
 
healthcheck:
  path: /api/health
  port: 3000
  interval: 10
  max_attempts: 30

Décortiquons les sections importantes :

  • service : le nom de votre application (utilisé pour nommer les conteneurs)
  • image : le chemin complet vers votre image Docker sur le registre
  • servers : liste des serveurs cibles avec les labels Traefik pour le routage
  • proxy : active le SSL automatique via Traefik et kamal-proxy
  • registry : vos identifiants pour le registre Docker
  • env : variables d'environnement (claires et secrètes séparées)
  • healthcheck : Kamal vérifie que votre app répond avant de basculer le trafic

4.3 Configurer les secrets

Éditez le fichier .kamal/secrets :

KAMAL_REGISTRY_PASSWORD=votre-mot-de-passe-docker-hub
DATABASE_URL=postgresql://user:pass@db-host:5432/mydb

Ce fichier ne doit jamais être commité dans Git. Vérifiez que .kamal/secrets est dans votre .gitignore.

4.4 Créer le endpoint de healthcheck

Kamal utilise un healthcheck pour vérifier que votre application est prête avant de basculer le trafic. Créez un endpoint API simple :

// app/api/health/route.ts
import { NextResponse } from 'next/server'
 
export async function GET() {
  return NextResponse.json({
    status: 'ok',
    timestamp: new Date().toISOString(),
  })
}

Étape 5 : Comprendre le déploiement zéro downtime

Avant de lancer le premier déploiement, comprenons comment Kamal 2 assure le zéro downtime :

Le flux de déploiement

1. Build de l'image Docker localement (ou en CI)
2. Push de l'image vers le registre
3. Pull de l'image sur le(s) serveur(s)
4. Lancement du nouveau conteneur
5. Healthcheck : Kamal attend que /api/health réponde 200
6. kamal-proxy bascule le trafic vers le nouveau conteneur
7. Arrêt gracieux de l'ancien conteneur

Le point clé est l'étape 6 : le trafic ne bascule qu'après que le nouveau conteneur a passé le healthcheck. Cela signifie que vos utilisateurs ne verront jamais une erreur 502 ou une page blanche pendant le déploiement.

kamal-proxy vs Traefik

Kamal 2 utilise kamal-proxy, un reverse proxy léger développé spécifiquement pour Kamal. Il remplace la dépendance à Traefik pour le routage de base, bien que Traefik reste disponible comme option pour les configurations avancées (multi-service, middlewares, etc.).

kamal-proxy gère :

  • Le basculement de trafic entre ancien et nouveau conteneur
  • La terminaison SSL avec Let's Encrypt
  • Les healthchecks automatiques
  • Le draining des connexions existantes

Étape 6 : Premier déploiement

6.1 Lancer le setup initial

Le premier déploiement nécessite une étape de setup qui configure Docker et kamal-proxy sur le serveur :

kamal setup

Cette commande effectue les opérations suivantes :

  1. Connexion SSH au serveur
  2. Installation de Docker (si non présent)
  3. Installation de kamal-proxy
  4. Login au registre Docker
  5. Build et push de l'image
  6. Pull et démarrage du conteneur
  7. Configuration du SSL

Vous verrez une sortie détaillée de chaque étape :

  INFO [f97da026] Running docker login ...
  INFO [f97da026] Finished in 1.337 seconds
  INFO Building image ...
  INFO Pushing image ...
  INFO [48716498] Running docker pull ...
  INFO [48716498] Finished in 8.234 seconds
  INFO [48716498] Running docker run ...
  INFO Waiting for healthy container ...
  INFO Container is healthy!

6.2 Vérifier le déploiement

Une fois terminé, vérifiez que tout fonctionne :

# Vérifier les conteneurs en cours d'exécution
kamal details
 
# Voir les logs de l'application
kamal app logs
 
# Vérifier le statut du proxy
kamal proxy details

Visitez https://app.votre-domaine.com — votre application Next.js devrait être accessible avec un certificat SSL valide.

Étape 7 : Déploiements suivants

Pour les déploiements ultérieurs, une seule commande suffit :

kamal deploy

C'est tout. Kamal va :

  1. Construire la nouvelle image
  2. La pousser vers le registre
  3. La télécharger sur le serveur
  4. Lancer le nouveau conteneur
  5. Vérifier le healthcheck
  6. Basculer le trafic
  7. Arrêter l'ancien conteneur

Déployer sans reconstruire l'image

Si vous avez déjà construit et poussé l'image (par exemple en CI) :

kamal deploy --skip-push

Étape 8 : Rollback

L'un des plus grands avantages de Kamal est la facilité de rollback. Si un déploiement cause des problèmes :

# Revenir à la version précédente
kamal rollback [TAG_DE_VERSION]

Pour voir les versions disponibles :

kamal app containers

Le rollback est quasi-instantané car l'image précédente est déjà présente sur le serveur. Kamal relance simplement l'ancien conteneur et bascule le trafic.

Étape 9 : Gérer les accessoires (bases de données, Redis, etc.)

Kamal peut gérer des services annexes appelés accessories. Ajoutez-les dans votre config/deploy.yml :

accessories:
  db:
    image: postgres:16-alpine
    host: votre-serveur.com
    port: "5432:5432"
    env:
      clear:
        POSTGRES_DB: mon_app
      secret:
        - POSTGRES_PASSWORD
    directories:
      - data:/var/lib/postgresql/data
    options:
      network: kamal
 
  redis:
    image: redis:7-alpine
    host: votre-serveur.com
    port: "6379:6379"
    directories:
      - data:/data
    options:
      network: kamal

Déployez les accessoires :

# Déployer tous les accessoires
kamal accessory boot all
 
# Ou un accessoire spécifique
kamal accessory boot db

Les accessoires sont persistants — ils ne sont pas redémarrés lors des déploiements de l'application principale.

Étape 10 : Déploiement multi-serveur

Pour une application à fort trafic, vous pouvez déployer sur plusieurs serveurs :

servers:
  web:
    hosts:
      - serveur-1.votre-domaine.com
      - serveur-2.votre-domaine.com
      - serveur-3.votre-domaine.com

Kamal déploie sur tous les serveurs en parallèle. Combiné avec un load balancer (comme un DigitalOcean Load Balancer ou un DNS round-robin), vous obtenez une haute disponibilité sans effort supplémentaire.

Rôles de serveur

Vous pouvez définir des rôles différents pour vos serveurs :

servers:
  web:
    hosts:
      - web-1.exemple.com
      - web-2.exemple.com
  worker:
    hosts:
      - worker-1.exemple.com
    cmd: npm run worker

Étape 11 : Intégration CI/CD avec GitHub Actions

Automatisez le déploiement avec GitHub Actions :

# .github/workflows/deploy.yml
name: Deploy
 
on:
  push:
    branches: [main]
 
concurrency:
  group: deploy
  cancel-in-progress: true
 
env:
  DOCKER_BUILDKIT: 1
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 20
 
    steps:
      - uses: actions/checkout@v4
 
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: "3.3"
          bundler-cache: true
 
      - name: Install Kamal
        run: gem install kamal
 
      - name: Set up SSH
        uses: webfactory/ssh-agent@v0.9.0
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
 
      - name: Deploy with Kamal
        env:
          KAMAL_REGISTRY_PASSWORD: ${{ secrets.DOCKER_HUB_TOKEN }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: kamal deploy

Ajoutez les secrets suivants dans votre dépôt GitHub :

  • SSH_PRIVATE_KEY : votre clé SSH privée pour le serveur
  • DOCKER_HUB_TOKEN : votre token Docker Hub
  • DATABASE_URL : la chaîne de connexion à la base de données

Étape 12 : Commandes utiles au quotidien

Voici les commandes Kamal que vous utiliserez le plus souvent :

Gestion de l'application

# Déployer
kamal deploy
 
# Voir les logs en temps réel
kamal app logs -f
 
# Exécuter une commande dans le conteneur
kamal app exec "node -e 'console.log(process.env.NODE_ENV)'"
 
# Ouvrir une session interactive
kamal app exec -i bash
 
# Redémarrer l'application
kamal app boot
 
# Arrêter l'application
kamal app stop

Gestion du proxy

# Voir la configuration du proxy
kamal proxy details
 
# Recharger la configuration
kamal proxy reboot

Gestion des accessoires

# Voir les logs de la base de données
kamal accessory logs db
 
# Redémarrer Redis
kamal accessory reboot redis
 
# Exécuter psql
kamal accessory exec db "psql -U postgres mon_app"

Maintenance

# Nettoyer les anciennes images Docker
kamal prune all
 
# Verrouiller les déploiements (maintenance)
kamal lock acquire -m "Maintenance en cours"
 
# Déverrouiller
kamal lock release

Étape 13 : Surveiller votre application

Healthcheck avancé

Améliorez votre endpoint de healthcheck pour vérifier les dépendances :

// app/api/health/route.ts
import { NextResponse } from 'next/server'
 
interface HealthStatus {
  status: string
  timestamp: string
  uptime: number
  checks: {
    database?: string
    memory?: {
      used: number
      total: number
    }
  }
}
 
export async function GET() {
  const health: HealthStatus = {
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    checks: {},
  }
 
  // Vérifier la mémoire
  const memUsage = process.memoryUsage()
  health.checks.memory = {
    used: Math.round(memUsage.heapUsed / 1024 / 1024),
    total: Math.round(memUsage.heapTotal / 1024 / 1024),
  }
 
  return NextResponse.json(health)
}

Monitoring externe

Pour un monitoring complet, vous pouvez ajouter un service de surveillance comme accessoire :

accessories:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    host: votre-serveur.com
    port: "3001:3001"
    directories:
      - data:/app/data
    options:
      network: kamal

Dépannage

Le healthcheck échoue

Si le déploiement échoue avec un timeout de healthcheck :

  1. Vérifiez les logs du conteneur :

    kamal app logs
  2. Testez le healthcheck localement :

    docker build -t test-app .
    docker run -p 3000:3000 test-app
    curl http://localhost:3000/api/health
  3. Augmentez le timeout dans deploy.yml :

    healthcheck:
      max_attempts: 60
      interval: 5

Erreur de permission SSH

Si Kamal ne peut pas se connecter au serveur :

# Vérifiez que votre clé est chargée
ssh-add -l
 
# Ajoutez votre clé si nécessaire
ssh-add ~/.ssh/id_ed25519
 
# Testez la connexion
ssh root@votre-serveur.com "echo OK"

Image trop volumineuse

Si le build ou le push prend trop de temps, optimisez votre Dockerfile :

  • Utilisez le multi-stage build (déjà fait dans notre Dockerfile)
  • Vérifiez votre .dockerignore
  • Utilisez node:20-alpine au lieu de node:20
  • Activez le BuildKit pour le cache de layers :
    export DOCKER_BUILDKIT=1

Problème de certificat SSL

Si le SSL ne fonctionne pas :

  1. Vérifiez que le DNS pointe bien vers le serveur :

    dig app.votre-domaine.com
  2. Vérifiez les logs du proxy :

    kamal proxy logs
  3. Assurez-vous que les ports 80 et 443 sont ouverts sur le firewall du serveur.

Kamal vs les alternatives

FonctionnalitéKamal 2CoolifyVercelKubernetes
Zéro downtimeOuiOuiOuiOui
SSL automatiqueOuiOuiOuiComplexe
Rollback1 commandeInterfaceAutomatiqueComplexe
Multi-serveurOuiOuiGéréOui
CoûtVPS seulementVPS seulementPar usageÉlevé
ComplexitéFaibleFaibleTrès faibleTrès élevée
Vendor lock-inAucunFaibleFortFaible
Interface webNonOuiOuiDashboard
CI/CD intégréNon (via GH Actions)OuiOuiNon

Kamal se distingue par sa simplicité et son absence de vendor lock-in. Un seul fichier YAML, quelques commandes, et votre application est en production. Pas de dashboard web à gérer, pas de service cloud à payer — juste SSH, Docker et votre serveur.

Prochaines étapes

Maintenant que votre application est déployée avec Kamal 2, voici comment aller plus loin :

  • Ajoutez un CDN comme Cloudflare devant votre serveur pour améliorer les performances
  • Configurez des alertes avec Uptime Kuma ou Better Stack pour être notifié en cas de problème
  • Mettez en place un environnement de staging avec un deuxième fichier de configuration (config/deploy.staging.yml)
  • Automatisez les backups de votre base de données avec un cron job sur le serveur
  • Explorez les hooks Kamal pour exécuter des scripts avant/après le déploiement (migrations de base de données, invalidation de cache, etc.)

Conclusion

Kamal 2 représente une approche rafraîchissante du déploiement : pas de complexité inutile, pas de vendor lock-in, pas de facture surprise. Avec un VPS à 5 euros par mois et quelques minutes de configuration, vous obtenez un pipeline de déploiement professionnel avec zéro downtime.

La philosophie de Kamal s'aligne parfaitement avec le mouvement croissant vers le self-hosting et la souveraineté numérique. Vous gardez le contrôle total de vos données et de votre infrastructure, tout en bénéficiant d'une expérience développeur fluide.

Que vous déployiez un side project, une startup ou une application d'entreprise, Kamal 2 vous offre la puissance nécessaire sans la complexité superflue. Et si demain vous devez changer de fournisseur de serveur, il suffit de modifier une ligne dans votre fichier de configuration.


Vous voulez lire plus de tutoriels? Découvrez notre dernier tutoriel sur 11 Les Bases de Laravel 11 : Generation d'URL.

Discutez de votre projet avec nous

Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.

Trouvons les meilleures solutions pour vos besoins.

Articles connexes

Docker Compose pour développeurs Full-Stack : Next.js, PostgreSQL et Redis

Apprenez à conteneuriser une application Next.js full-stack avec PostgreSQL et Redis en utilisant Docker Compose. Ce tutoriel pratique couvre l'orchestration multi-services, les workflows de développement, le rechargement à chaud, les health checks et les configurations prêtes pour la production.

28 min read·