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 -yPHP 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-redisOutils 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 pdftkOutils divers
sudo apt install -y git curl wget unzip nginx mysql-server certbot python3-certbot-nginx3. 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 uniquementVé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 --versionCloner 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-interactionDroits 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/tmpPremiè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 public5. 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 --versionInstaller 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 --versionBuild 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 build6. 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 --versionDémarrer l'application
cd /var/www/mon-projet/web
pm2 start npm --name "mon-app-front" -- start
pm2 statusConfigurer 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 processusCommandes 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 terminal7. 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_brevoFrontend — 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_interneGé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.pem8. 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 :
| Secret | Valeur |
|---|---|
| SSH_HOST | IP ou hostname du serveur |
| SSH_USER | Utilisateur SSH (ex: plateformweb) |
| SSH_PORT | Port SSH (défaut: 22) |
| SSH_KEY | Clé privée SSH (contenu de ~/.ssh/id_ed25519) |
| SSH_PASSPHRASE | Passphrase 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_ed25519Fichier .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.com11. Checklist finale
☐
PHP 8.3/8.4 + toutes les extensions
php8.4 -m☐
Composer installé globalement
composer --version☐
Ghostscript disponible
gs --version☐
QPDF disponible
qpdf --version☐
LibreOffice headless
libreoffice --headless --version☐
pdfinfo disponible
pdfinfo --version☐
Node.js LTS via NVM
node --version☐
npm fonctionnel
npm --version☐
PM2 installé globalement
pm2 --version☐
PM2 configuré en startup systemd
systemctl status pm2-plateformweb☐
.env.local Symfony configuré
APP_ENV=prod php8.4 bin/console debug:dotenv☐
Migrations appliquées
php8.4 bin/console doctrine:migrations:status☐
Cache prod warmup OK
APP_ENV=prod php8.4 bin/console cache:warmup☐
Next.js build OK
npm run build (dans web/)☐
Nginx config valide
nginx -t☐
HTTPS Let's Encrypt actif
certbot certificates☐
Secrets GitHub Actions configurés
Settings → Secrets