Vous en avez assez de déployer votre application à la main à chaque modification ? Un pipeline CI/CD bien configuré, c'est la différence entre une livraison stressante et un simple git push qui fait tout le travail à votre place. Dans cet article, on met en place un workflow GitHub Actions complet pour une stack Next.js + NestJS.
Pourquoi automatiser ses déploiements ?
Le déploiement manuel, c'est une source d'erreurs humaines : oublier de lancer les tests, pousser sur la mauvaise branche, oublier de rebuilder l'image Docker. Un pipeline CI/CD élimine ces risques et apporte plusieurs avantages concrets :
- Cohérence : chaque déploiement suit exactement la même séquence d'étapes
- Rapidité : du merge à la prod en quelques minutes, sans intervention humaine
- Confiance : les tests passent toujours avant que le code parte en production
- Historique : chaque run est tracé, avec logs complets en cas d'échec
Pour une stack Next.js + NestJS, on va distinguer deux workflows distincts : un pour le frontend, un pour le backend.
Architecture du projet et organisation des workflows
Partons d'une structure monorepo classique, avec le frontend et le backend dans le même dépôt GitHub :
mon-projet/
├── frontend/ # Next.js App Router
├── backend/ # NestJS
└── .github/
└── workflows/
├── frontend.yml
└── backend.yml
GitHub Actions détecte automatiquement les fichiers dans .github/workflows/ et les exécute selon les déclencheurs que vous définissez (push, pull_request, schedule, etc.).
Workflow CI/CD pour Next.js
Créez le fichier .github/workflows/frontend.yml :
name: Frontend CI/CD
on:
push:
branches: [main]
paths:
- 'frontend/**'
pull_request:
branches: [main]
paths:
- 'frontend/**'
jobs:
lint-and-test:
name: Lint & Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./frontend
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Run type check
run: npm run type-check
- name: Run tests
run: npm run test -- --passWithNoTests
- name: Build
run: npm run build
env:
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
deploy:
name: Deploy to Vercel
needs: lint-and-test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
working-directory: ./frontend
vercel-args: '--prod'
Quelques points importants dans ce workflow :
paths : le workflow ne se déclenche que si des fichiers dans frontend/ ont été modifiés. Inutile de relancer le build du frontend si seul le backend a changé.
needs : le job deploy attend que lint-and-test soit passé avec succès. Si les tests échouent, le déploiement ne part jamais.
if : on ne déploie en production que sur un push sur main. Les pull requests déclenchent uniquement le lint et les tests.
Workflow CI/CD pour NestJS
Pour le backend, la logique est similaire mais le déploiement cible souvent un VPS ou un service comme Railway/Render. Voici un exemple avec une image Docker :
name: Backend CI/CD
on:
push:
branches: [main]
paths:
- 'backend/**'
pull_request:
branches: [main]
paths:
- 'backend/**'
jobs:
lint-and-test:
name: Lint & Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./backend
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: backend/package-lock.json
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Run type check
run: npm run build
- name: Run unit tests
run: npm run test
- name: Run e2e tests
run: npm run test:e2e
env:
MONGODB_URI: ${{ secrets.TEST_MONGODB_URI }}
build-and-push:
name: Build & Push Docker Image
needs: lint-and-test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./backend
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/mon-backend:latest
${{ secrets.DOCKER_USERNAME }}/mon-backend:${{ github.sha }}
deploy:
name: Deploy to VPS
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
docker pull ${{ secrets.DOCKER_USERNAME }}/mon-backend:latest
docker stop mon-backend || true
docker rm mon-backend || true
docker run -d \
--name mon-backend \
--restart unless-stopped \
-p 3001:3001 \
-e MONGODB_URI=${{ secrets.MONGODB_URI }} \
-e JWT_SECRET=${{ secrets.JWT_SECRET }} \
${{ secrets.DOCKER_USERNAME }}/mon-backend:latest
Ce workflow en trois jobs enchaîne : tests → build de l'image Docker → déploiement SSH sur le VPS. Si l'une des étapes échoue, la chaîne s'arrête.
Gérer les secrets GitHub
Toutes les valeurs sensibles (tokens, clés SSH, URIs de base de données) doivent être stockées dans GitHub Secrets, jamais dans le code. Pour les configurer : Settings → Secrets and variables → Actions → New repository secret.
Pour une stack Next.js + NestJS, vous aurez typiquement besoin de :
# Frontend (Vercel)
VERCEL_TOKEN
VERCEL_ORG_ID
VERCEL_PROJECT_ID
NEXT_PUBLIC_API_URL
# Backend (Docker + VPS)
DOCKER_USERNAME
DOCKER_PASSWORD
VPS_HOST
VPS_USER
VPS_SSH_KEY
MONGODB_URI
JWT_SECRET
TEST_MONGODB_URI
Une bonne pratique : utilisez des secrets d'environnement distincts pour staging et production. GitHub permet de créer des environnements avec leurs propres secrets et des règles de protection (approbation manuelle avant déploiement en prod, par exemple).
Optimiser la vitesse du pipeline
Un pipeline lent devient vite frustrant. Voici trois techniques pour garder des temps de run raisonnables :
Cache des dépendances npm
L'option cache: 'npm' sur actions/setup-node met automatiquement en cache le répertoire node_modules entre les runs, en se basant sur le hash du package-lock.json. Un gain de 1 à 2 minutes sur les projets avec beaucoup de dépendances.
Cache Docker BuildKit
Pour les builds Docker, activez le cache des layers :
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./backend
push: true
tags: mon-image:latest
cache-from: type=gha
cache-to: type=gha,mode=max
Jobs parallèles
Si vous avez des vérifications indépendantes (lint TypeScript, tests unitaires, tests e2e), lancez-les en parallèle plutôt qu'en séquence. GitHub Actions exécute des jobs sans needs simultanément.
Notifications et observabilité
Un pipeline CI/CD silencieux ne sert à rien si personne n'est alerté en cas d'échec. Ajoutez une notification Slack ou par email en fin de workflow :
- name: Notify on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "❌ Déploiement échoué sur *${{ github.repository }}* (branche `${{ github.ref_name }}`)\nVoir les logs : ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
La condition if: failure() garantit que cette étape ne s'exécute qu'en cas d'échec, évitant les notifications parasites.
Pour aller plus loin
Une fois ce pipeline en place, plusieurs améliorations sont possibles selon vos besoins : déploiements conditionnels par environnement (staging/prod), preview deployments automatiques sur les pull requests avec Vercel, matrix builds pour tester sur plusieurs versions de Node.js, ou encore intégration d'un scan de sécurité des dépendances avec npm audit.
L'automatisation du déploiement, c'est un investissement initial de quelques heures qui vous fait gagner du temps à chaque livraison — et surtout, vous enlève une source de stress à chaque mise en production.
Vous montez une stack Next.js + NestJS et vous voulez partir sur de bonnes bases dès le départ ? C'est exactement le genre de projet sur lequel j'interviens. Contactez-moi pour qu'on en discute.