ITSkillsCenter
Blog

Déployer une API NestJS 11 sur Coolify v4

12 min de lecture

Coolify v4 est devenu en 2026 le PaaS auto-hébergé de référence pour les startups qui veulent éviter la facture de Heroku, Render ou Vercel tout en gardant un workflow git push to deploy. La beta 470 atteint 94 % du milestone v4 stable et des dizaines de milliers d’instances tournent en production sur des VPS Hetzner, OVH ou DigitalOcean. Ce tutoriel déploie une API NestJS 11 complète sur Coolify : VPS Ubuntu, Coolify installé, Postgres et Redis managés, Dockerfile multi-stage, déploiement depuis GitHub, certificats Let’s Encrypt automatiques, sauvegardes planifiées.

📍 Article principal : NestJS 11 pour startup : architecture production 2026. Ce tutoriel finalise la chaîne en mettant en ligne tout ce que les autres tutoriels du parcours ont mis en place localement.

Prérequis

  • Un VPS Ubuntu 24.04 LTS avec au moins 4 Go de RAM et 40 Go de disque (Hetzner CX23, OVH VPS Comfort, DigitalOcean Premium)
  • Un nom de domaine avec accès au DNS
  • API NestJS 11 packagée dans un Dockerfile (suit dans ce tutoriel)
  • Compte GitHub avec le dépôt du code
  • Temps estimé : 2 heures

Étape 1 — Préparer le VPS

Coolify exige un Ubuntu 22.04 ou 24.04 récent avec un utilisateur non-root configuré pour sudo sans mot de passe. Ouvrir uniquement les ports indispensables (22, 80, 443, 6001 pour l’interface admin) avec UFW. Mettre à jour les paquets et installer Docker — le script d’installation Coolify peut le faire automatiquement, mais le faire en amont laisse plus de contrôle.

sudo apt update && sudo apt upgrade -y
sudo ufw allow OpenSSH
sudo ufw allow http
sudo ufw allow https
sudo ufw allow 6001/tcp
sudo ufw enable
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER

Le port 6001 est l’interface web Coolify ; on peut le restreindre par IP source via ufw allow from x.x.x.x to any port 6001 si on veut limiter l’accès admin. La déconnexion-reconnexion SSH est nécessaire pour que le groupe Docker soit pris en compte. Vérifier avec docker ps qui doit lister les conteneurs sans demander sudo.

Étape 2 — Installer Coolify

L’installation se fait avec un script officiel maintenu par l’équipe Coolify. Il télécharge les images Docker nécessaires, configure un docker-compose.yml dans /data/coolify, génère les clés SSH internes, et démarre l’interface web. Le processus prend cinq minutes sur un VPS standard.

curl -fsSL https://cdn.coollabs.io/coolify/install.sh | sudo bash

À la fin du script, l’URL d’accès est affichée — généralement http://<ip-vps>:6001. Le premier accès demande de créer un compte administrateur. Une fois créé, configurer immédiatement deux choses : le sous-domaine de l’instance (coolify.example.com) avec un certificat Let’s Encrypt, et l’authentification à deux facteurs sur le compte admin. Sans 2FA, un VPS exposé à Internet avec une UI admin est un risque majeur.

Étape 3 — Connecter GitHub et créer un projet

Coolify se connecte à GitHub via une GitHub App qu’on installe en quelques clics depuis l’interface. Cette App reçoit les webhooks de push et déclenche les déploiements automatiquement. Le scope est limité au dépôt qu’on choisit, ce qui évite de donner accès à toute l’organisation GitHub.

Dans Coolify, créer un nouveau projet Acme API et ajouter une ressource Application. Sélectionner GitHub comme source, choisir le dépôt et la branche main. Coolify détecte automatiquement la présence d’un Dockerfile ou propose Nixpacks par défaut. Pour NestJS, un Dockerfile maîtrisé donne de meilleurs résultats que Nixpacks qui peut produire une image gonflée.

Étape 4 — Écrire un Dockerfile multi-stage

Le pattern multi-stage minimise la taille de l’image finale en isolant la phase de build (qui a besoin du compilateur TypeScript et des dépendances de dev) de la phase de runtime (qui n’a besoin que du dist et des dépendances de prod). Sur une API NestJS, ce pattern fait passer l’image de 1,2 Go à 200 Mo, ce qui accélère drastiquement les déploiements.

# Dockerfile
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
RUN pnpm prune --prod

FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/prisma ./prisma
EXPOSE 3000
CMD ["node", "dist/main.js"]

Trois choix méritent commentaire. node:22-alpine reste le compromis taille/compatibilité standard — Alpine pèse environ 150 Mo contre plus de 400 Mo pour node:22 Debian (et 240 Mo pour node:22-slim). pnpm prune --prod retire les dépendances de développement après le build, ce qui réduit considérablement le runtime. Le dossier prisma/ est copié pour permettre l’exécution de npx prisma migrate deploy au démarrage.

Étape 5 — Configurer les variables d’environnement et les secrets

Toutes les variables sensibles (JWT_SECRET, S3_SECRET_KEY, DATABASE_URL) se déclarent dans l’interface Coolify, jamais dans le dépôt git. Coolify les injecte au runtime du conteneur. Elles sont chiffrées en base et masquées dans l’UI, mais accessibles aux opérateurs qui ont les droits sur le projet — d’où l’importance d’une bonne gestion des permissions.

NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://acme:****@postgres:5432/acme
REDIS_HOST=redis
REDIS_PORT=6379
JWT_SECRET=<généré avec openssl rand -hex 32>
S3_ENDPOINT=https://<account>.r2.cloudflarestorage.com
S3_ACCESS_KEY=...
S3_SECRET_KEY=...
S3_BUCKET=acme-prod

L’astuce qui sauve : générer JWT_SECRET avec openssl rand -hex 32 directement sur le VPS et le copier-coller dans Coolify. Ne jamais le générer sur sa machine de développement et le commit dans un dépôt — même privé, un dépôt git fuit régulièrement par accident. Le rotation périodique de ce secret (tous les 6 mois) invalide tous les tokens existants, ce qui force tous les utilisateurs à se reconnecter — opération à planifier en heure creuse.

Étape 6 — Provisionner Postgres et Redis managés

Coolify supporte les bases de données managées via ses templates. Ajouter une nouvelle ressource Postgres dans le projet, choisir la version 17-alpine, configurer les credentials. Coolify crée le conteneur, configure la persistance volume, et expose le hostname <projet>-<hash> au reste du projet via le réseau Docker interne. Aucun port n’est exposé sur Internet par défaut, ce qui est le comportement souhaité.

Faire de même pour Redis 8. Une fois les deux services healthy dans l’UI, mettre à jour les variables DATABASE_URL et REDIS_HOST de l’API avec les hostnames internes générés. La résolution DNS Docker entre conteneurs du même projet fonctionne automatiquement. Pour les recommandations spécifiques à PostgreSQL en production avec Coolify, le tutoriel PostgreSQL managé avec Coolify couvre les détails de tuning.

Étape 7 — Configurer le domaine et les certificats

Pointer un sous-domaine api.acme.io vers l’IP du VPS via un enregistrement A. Dans Coolify, déclarer ce domaine sur l’application, activer SSL via Let’s Encrypt — Coolify utilise Traefik en arrière-plan, qui gère automatiquement la demande, le renouvellement et le rechargement des certificats. La propagation DNS et l’émission du certificat prennent quelques minutes.

# vérification DNS depuis le terminal
dig +short api.acme.io
# vérification certificat
curl -vI https://api.acme.io 2>&1 | grep "issuer\|subject"

Une fois le certificat émis, Traefik redirige automatiquement HTTP vers HTTPS et applique HSTS via les headers de sécurité Coolify. Pour les API consommées par un front sur un autre domaine, configurer également les headers CORS dans le code NestJS — Traefik ne s’en occupe pas. Le module @nestjs/common expose app.enableCors({ origin: ['https://app.acme.io'] }) dans main.ts.

Étape 8 — Healthchecks, redémarrages et sauvegardes

Coolify utilise les healthchecks Docker natifs. Ajouter dans le Dockerfile une instruction HEALTHCHECK qui appelle l’endpoint /health/live de NestJS. Si la sonde échoue trois fois consécutives, Coolify redémarre le conteneur automatiquement. Cette discipline évite qu’une fuite mémoire ou un deadlock ne maintienne un service zombie en ligne.

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

Pour les sauvegardes, Coolify intègre des backups planifiés vers S3-compatible. Configurer une sauvegarde nocturne de la base Postgres vers un bucket distinct du stockage applicatif — la séparation évite qu’une compromission du bucket app ne touche les sauvegardes. Tester le restore au moins une fois : une sauvegarde non testée n’existe pas. La bonne pratique est de restaurer mensuellement vers un environnement de staging et de vérifier que l’API démarre sur ces données.

Étape 9 — Pipeline GitHub Actions et déploiement zero-downtime

Pour les déploiements sérieux, ne pas laisser Coolify builder l’image sur le VPS de production. Le build dans GitHub Actions, qui pousse l’image vers GitHub Container Registry, puis Coolify qui consomme cette image — ce flow découple la compilation des ressources de production et accélère les déploiements à 30 secondes au lieu de 3-4 minutes.

# .github/workflows/deploy.yml
on: { push: { branches: [main] } }
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with: { registry: ghcr.io, username: ${{ github.actor }}, password: ${{ secrets.GITHUB_TOKEN }} }
      - uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/acme/api:${{ github.sha }}
      - run: curl -X POST "https://coolify.acme.io/api/v1/deploy?uuid=${{ secrets.COOLIFY_APP_UUID }}" -H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}"

Le webhook Coolify reçoit la commande, pull la nouvelle image, lance un nouveau conteneur en parallèle de l’ancien, attend que les healthchecks passent, puis bascule le trafic via Traefik et arrête l’ancien. La fenêtre où les deux conteneurs cohabitent est de quelques secondes — invisible pour les utilisateurs.

Erreurs fréquentes

Erreur Cause Solution
Build qui échoue après un push Lockfile désynchronisé Régénérer pnpm-lock localement et commit
Migrations Prisma non appliquées Ordre démarrage versus migration Init container ou commande pre-deploy
HTTPS non émis DNS pas encore propagé Attendre + vérifier dig
Mémoire insuffisante au build VPS 2 Go avec build NestJS lourd Build externe via GitHub Actions et push image
Logs perdus après redéploiement Pas de driver de log distant Loki ou Grafana Cloud sink

L’ordre démarrage versus migration est un piège classique. Quand Coolify démarre le nouveau conteneur, NestJS tente de se connecter à la base et de servir les requêtes immédiatement, alors que prisma migrate deploy n’a pas encore tourné. Deux solutions cohabitent : un script start.sh qui exécute la migration avant node dist/main.js, ou un init container Coolify qui s’exécute avant le service principal. Le script est plus simple à mettre en place pour démarrer.

Observabilité et logs centralisés

Les logs des conteneurs Coolify se consultent dans l’UI mais sont volatiles. Pour conserver l’historique et alerter, brancher un driver de log Loki via le docker daemon : tous les stdout/stderr sont alors poussés vers une instance Grafana Loki distante. La référence pour cette mise en place se trouve dans Envoyer les logs applicatifs vers Loki via OTLP. Pour la corrélation traces-logs, l’instrumentation OpenTelemetry de l’API NestJS est documentée dans Instrumenter Node.js avec OpenTelemetry SDK. Cette stack LGTM (Loki, Grafana, Tempo, Mimir) tourne elle-même très bien sur Coolify.

Maintenance et mises à jour

Coolify se met à jour via un bouton dans l’UI ou via la CLI coolify update. Les mises à jour beta sortent plusieurs fois par semaine — la prudence consiste à attendre quelques jours avant d’appliquer chaque release pour laisser remonter d’éventuelles régressions sur le forum communautaire.

FAQ

Coolify ou Dokku ?
Les deux sont open-source et auto-hébergés. Coolify a une UI moderne et une communauté plus active en 2026, Dokku est plus austère mais réputé stable. Pour un nouveau projet, Coolify est le choix par défaut. Pour une équipe DevOps confirmée qui veut tout en CLI, Dokku reste compétitif.

Combien coûte un déploiement complet ?
VPS Hetzner CX23 à 3,99 €/mois (2 vCPU, 4 Go RAM, 40 Go SSD) supporte une API NestJS modeste avec Postgres + Redis. Pour un trafic plus important, le CX32 reste sous les 10 €/mois. Note : Hetzner a annoncé un ajustement tarifaire au 1er avril 2026 sur certaines gammes — vérifier le tarif courant avant l’achat. Coolify n’a aucun coût de licence — c’est gratuit pour un usage personnel comme professionnel. Le seul coût additionnel est le domaine (10 €/an) et le stockage S3 (variable).

Comment scaler horizontalement ?
Coolify supporte les services en plusieurs replicas via le compose interne, mais le load balancing entre instances reste basique. Pour une scalabilité sérieuse au-delà de 3-4 instances, basculer vers un orchestrateur dédié (Kubernetes, Nomad, ECS) reste plus pertinent. Coolify est excellent pour les projets entre 1 et 5 instances.

Migration depuis Heroku ou Render ?
La migration consiste essentiellement à reconstituer le manifest Procfile en Dockerfile et à exporter les variables d’environnement. Pour les bases de données, pg_dump sur l’ancienne plateforme et pg_restore sur la nouvelle. Le DNS bascule en dernier, après vérification que tout fonctionne sur Coolify avec un sous-domaine de test.

Tutoriels associés

Références

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité