Déployer Symfony + Next.js
Navigation

1. Vue d'ensemble

Ce guide couvre le déploiement d'une stack Symfony (API REST)Next.js (App Router) sur un serveur Linux (Debian/Ubuntu).

  • Backend — Symfony 7+, PHP 8.3/8.4, MySQL/MariaDB, Apache ou Nginx
  • Frontend — Next.js 15+, Node.js 20 LTS, PM2, Nginx en reverse proxy
  • CI/CD — GitHub Actions via SSH (appleboy/ssh-action)
  • Outils PDF — Ghostscript, QPDF, LibreOffice, Poppler (pdfinfo)

2. Dépendances système (apt)

Mise à jour du système

sudo apt update && sudo apt upgrade -y

PHP 8.4 + extensions requises

sudo apt install -y   php8.4 php8.4-cli php8.4-fpm   php8.4-mysql php8.4-pgsql   php8.4-intl php8.4-mbstring   php8.4-xml php8.4-curl   php8.4-zip php8.4-gd   php8.4-opcache php8.4-bcmath   php8.4-redis

Outils PDF (Ghostscript, QPDF, LibreOffice, Poppler)

# Ghostscript — compression PDF
sudo apt install -y ghostscript

# QPDF — manipulation PDF (split, merge, protect, repair…)
sudo apt install -y qpdf

# LibreOffice — conversion Office → PDF (headless)
sudo apt install -y libreoffice libreoffice-writer libreoffice-calc   libreoffice-impress libreoffice-draw libreoffice-headless

# Poppler-utils — pdfinfo, pdfimages…
sudo apt install -y poppler-utils

# pdftk (optionnel — signer/remplir formulaires)
sudo apt install -y pdftk

Outils divers

sudo apt install -y   git curl wget unzip   nginx mysql-server   certbot python3-certbot-nginx

3. Configuration PHP

Éditez /etc/php/8.4/cli/php.ini et /etc/php/8.4/fpm/php.ini :

memory_limit = 512M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 120
date.timezone = Europe/Paris
opcache.enable = 1
opcache.memory_consumption = 256
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 0  ; prod uniquement

Vérifier les extensions chargées :

php8.4 -m | grep -E "intl|mbstring|pdo|zip|gd|xml|curl|opcache"

4. Composer & Symfony

Installer Composer globalement

curl -sS https://getcomposer.org/installer | php8.4
sudo mv composer.phar /usr/local/bin/composer
sudo chmod +x /usr/local/bin/composer
composer --version

Cloner et installer le projet

cd /var/www
git clone https://github.com/votre-org/votre-repo.git mon-projet
cd mon-projet
composer install --no-dev --optimize-autoloader --no-interaction

Droits et répertoires requis

# L'utilisateur web (www-data, ou votre user dédié) doit posséder ces dossiers
sudo chown -R www-data:www-data var/ public/tmp/ public/build/
sudo chmod -R 775 var/ public/tmp/

# Créer les répertoires si inexistants
mkdir -p var/log var/cache public/tmp

Première mise en prod

# Copier et configurer .env
cp .env .env.local
# Éditer .env.local avec vos valeurs prod (DATABASE_URL, APP_SECRET, etc.)

APP_ENV=prod php8.4 bin/console doctrine:migrations:migrate --no-interaction
APP_ENV=prod php8.4 bin/console cache:warmup
APP_ENV=prod php8.4 bin/console assets:install public

5. Node.js via NVM

NVM (Node Version Manager) permet de gérer plusieurs versions de Node.js par utilisateur sans droits root.

Installation NVM

# En tant qu'utilisateur dédié (ex: plateformweb)
su - plateformweb

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash

# Recharger le shell
source ~/.bashrc

# Vérifier
nvm --version

Installer Node.js LTS

nvm install --lts
nvm use --lts
nvm alias default node   # définit la version par défaut

node --version   # v22.x ou v20.x
npm --version

Build du projet Next.js

cd /var/www/mon-projet/web

# Copier et configurer les variables d'environnement Next.js
cp .env.example .env.local
# Éditer .env.local : NEXT_PUBLIC_API_URL, etc.

npm ci
npm run build

6. PM2 — process manager

PM2 maintient le processus Next.js actif, le redémarre en cas de crash et l'intègre au démarrage système via systemd.

Installer PM2

npm install -g pm2
pm2 --version

Démarrer l'application

cd /var/www/mon-projet/web

pm2 start npm --name "mon-app-front" -- start
pm2 status

Configurer le démarrage automatique (systemd)

# Générer la commande systemd (à exécuter en root)
pm2 startup systemd -u plateformweb --hp /home/plateformweb

# Copier-coller la commande sudo générée, puis :
pm2 save   # sauvegarder la liste des processus

Commandes utiles PM2

pm2 status                          # liste des processus
pm2 logs mon-app-front              # logs en direct
pm2 restart mon-app-front           # redémarrer
pm2 reload mon-app-front            # rechargement 0-downtime
pm2 stop mon-app-front              # stopper
pm2 delete mon-app-front            # supprimer
pm2 monit                           # tableau de bord terminal

7. Variables d'environnement

Backend — .env.local (Symfony)

APP_ENV=prod
APP_SECRET=votre_secret_32_chars_minimum

# Base de données
DATABASE_URL="mysql://user:password@127.0.0.1:3306/ma_base?serverVersion=8.0"

# Binaires PDF
GHOSTSCRIPT_BIN=/usr/bin/gs
QPDF_BIN=/usr/bin/qpdf
LIBREOFFICE_BIN=/usr/bin/libreoffice
PDFINFO_BIN=/usr/bin/pdfinfo

# JWT (si API sécurisée)
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=votre_passphrase

# Frontend (URL du front Next.js)
FRONTEND_URL=https://app.votre-domaine.com

# Mailer (Brevo/SMTP)
MAILER_DSN=smtp://user:pass@smtp-relay.brevo.com:587
API_KEY_BREVO=votre_cle_brevo

Frontend — web/.env.local (Next.js)

# URL de l'API Symfony
NEXT_PUBLIC_API_URL=https://api.votre-domaine.com

# Optionnel — pour les appels serveur (SSR/Server Actions)
API_INTERNAL_URL=http://127.0.0.1:8080
INTERNAL_API_SECRET=votre_secret_interne

Générer les clés JWT

mkdir -p config/jwt
openssl genrsa -out config/jwt/private.pem -aes256 4096
openssl rsa -pubout -in config/jwt/private.pem -out config/jwt/public.pem
chmod 600 config/jwt/private.pem

8. Script de déploiement

Script complet à placer dans .github/workflows/deploy.yml ou à exécuter manuellement sur le serveur.

#!/bin/bash
set -e

PROJECT_DIR="/var/www/mon-projet"
WEB_DIR="$PROJECT_DIR/web"
PHP="/usr/bin/php8.4"
COMPOSER="/usr/local/bin/composer"

echo "==> Git pull"
cd "$PROJECT_DIR"
git fetch origin master
git reset --hard origin/master

echo "==> Symfony — dépendances"
$PHP $COMPOSER install --no-dev --optimize-autoloader --no-interaction

echo "==> Symfony — migrations"
APP_ENV=prod $PHP bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration

echo "==> Symfony — cache"
APP_ENV=prod $PHP bin/console cache:clear
APP_ENV=prod $PHP bin/console cache:warmup

echo "==> Next.js — dépendances & build"
export NVM_DIR="$HOME/.nvm"
if [ -s "$NVM_DIR/nvm.sh" ]; then
. "$NVM_DIR/nvm.sh"
fi
cd "$WEB_DIR"
npm ci --prefer-offline
npm run build

echo "==> Next.js — restart PM2"
pm2 restart mon-app-front || pm2 start npm --name "mon-app-front" -- start
pm2 save

echo "==> Déploiement terminé ✓"

9. GitHub Actions (CI/CD)

Secrets GitHub à configurer

Dans Settings → Secrets and variables → Actions :

SecretValeur
SSH_HOSTIP ou hostname du serveur
SSH_USERUtilisateur SSH (ex: plateformweb)
SSH_PORTPort SSH (défaut: 22)
SSH_KEYClé privée SSH (contenu de ~/.ssh/id_ed25519)
SSH_PASSPHRASEPassphrase de la clé SSH si protégée

Générer une clé SSH dédiée au déploiement

# Sur votre machine locale
ssh-keygen -t ed25519 -C "deploy@github-actions" -f ~/.ssh/deploy_id_ed25519

# Copier la clé publique sur le serveur
ssh-copy-id -i ~/.ssh/deploy_id_ed25519.pub plateformweb@votre-serveur

# La clé PRIVÉE va dans le secret GitHub SSH_KEY
cat ~/.ssh/deploy_id_ed25519

Fichier .github/workflows/deploy.yml

name: Deploy

on:
push:
branches: [master]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
port: ${{ secrets.SSH_PORT }}
key: ${{ secrets.SSH_KEY }}
passphrase: ${{ secrets.SSH_PASSPHRASE }}
script_stop: true
script: |
set -e
cd /var/www/mon-projet

echo "==> Git pull"
git fetch origin master
git reset --hard origin/master

echo "==> Symfony"
/usr/bin/php8.4 /usr/local/bin/composer install \
--no-dev --optimize-autoloader --no-interaction
APP_ENV=prod /usr/bin/php8.4 bin/console \
doctrine:migrations:migrate --no-interaction --allow-no-migration
APP_ENV=prod /usr/bin/php8.4 bin/console cache:clear
APP_ENV=prod /usr/bin/php8.4 bin/console cache:warmup

echo "==> Next.js"
export NVM_DIR="$HOME/.nvm"
if [ -s "$NVM_DIR/nvm.sh" ]; then . "$NVM_DIR/nvm.sh"; fi
cd /var/www/mon-projet/web
npm ci --prefer-offline
npm run build

echo "==> PM2"
pm2 restart mon-app-front || \
pm2 start npm --name "mon-app-front" -- start
pm2 save
⚠ Piège fréquent : ne jamais utiliser [ condition ] && cmdavec set -e — si la condition est fausse, le script s'arrête. Toujours préférer if [ condition ]; then cmd; fi.

10. Reverse proxy Nginx

Backend Symfony — /etc/nginx/sites-available/api.conf

server {
listen 80;
server_name api.votre-domaine.com;
root /var/www/mon-projet/public;
index index.php;

location / {
try_files $uri /index.php$is_args$args;
}

location ~ ^/index\.php(/|$) {
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
internal;
}

location ~ \.php$ { return 404; }

client_max_body_size 64M;
access_log /var/log/nginx/api.access.log;
error_log  /var/log/nginx/api.error.log;
}

Frontend Next.js — /etc/nginx/sites-available/app.conf

server {
listen 80;
server_name app.votre-domaine.com;

location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}

access_log /var/log/nginx/app.access.log;
error_log  /var/log/nginx/app.error.log;
}

Activer les vhosts et HTTPS (Let's Encrypt)

sudo ln -s /etc/nginx/sites-available/api.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

# HTTPS automatique via Certbot
sudo certbot --nginx -d api.votre-domaine.com -d app.votre-domaine.com

11. Checklist finale

PHP 8.3/8.4 + toutes les extensionsphp8.4 -m
Composer installé globalementcomposer --version
Ghostscript disponiblegs --version
QPDF disponibleqpdf --version
LibreOffice headlesslibreoffice --headless --version
pdfinfo disponiblepdfinfo --version
Node.js LTS via NVMnode --version
npm fonctionnelnpm --version
PM2 installé globalementpm2 --version
PM2 configuré en startup systemdsystemctl status pm2-plateformweb
.env.local Symfony configuréAPP_ENV=prod php8.4 bin/console debug:dotenv
Migrations appliquéesphp8.4 bin/console doctrine:migrations:status
Cache prod warmup OKAPP_ENV=prod php8.4 bin/console cache:warmup
Next.js build OKnpm run build (dans web/)
Nginx config validenginx -t
HTTPS Let's Encrypt actifcertbot certificates
Secrets GitHub Actions configurésSettings → Secrets