Guide de déploiement prod : VM Debian nue (sans Docker), service systemd, déploiement par push Git (bare repo + post-receive), branchement rsync/SSH en lecture seule à la prod Grav, reverse proxy HTTPS, dimensionnement VM.
18 KiB
Déploiement en production
Ce document décrit comment déployer le générateur de portfolios sur une VM Debian nue (sans Docker) et le brancher, en lecture seule, à la VM Grav de production de Figures Libres.
Le déploiement se fait par push Git vers un dépôt bare sur la VM (même principe que les autres projets Figures Libres : https://figureslibres.io/dokuwiki/developement:git_deployement). L'app tourne comme service systemd (serveur Nitro), derrière un reverse proxy HTTPS.
⚠️ Exception à la règle « tout en Docker ». Docker n'est utilisé qu'en développement local. En production, l'app tourne nativement sur Debian.
1. Architecture cible
Poste dev VM « portfolios » (Debian nue) VM Grav (prod)
┌───────────┐ git push prod ┌──────────────────────────────────────┐ rsync/SSH ┌──────────────┐
│ generator │ ─────────────────▶ │ bare repo .git ──post-receive──▶ deploy│ ─(lecture ────▶│ user/pages/ │
│ (repo) │ │ → checkout + npm ci + build │ seule) │ 02.projets/ │
└───────────┘ │ → systemctl restart │◀──── pull ──────│ (inchangé) │
│ │ └──────────────┘
│ systemd: node .output/server/index.mjs │
│ écoute 127.0.0.1:3000 │
│ données: /var/lib/portfolio-generator/ │
└──────────────┬─────────────────────────┘
│ 127.0.0.1:3000
┌────────▼─────────┐
│ nginx :443 (TLS) │ ◀── navigateurs (équipe + liens publics)
└──────────────────┘
Points clés :
- Le contenu ne circule que dans un sens : la VM portfolios tire les projets
depuis la prod Grav par
rsync/SSH en lecture seule. La prod n'est jamais modifiée et ignore l'existence de l'app. - On ne réimporte pas
grav-referenceen prod (c'est un jeu d'exemple de dev). Le contenu prod vit dans un cache local rafraîchi par le bouton « Rafraîchir ». - Le rendu PDF appelle le serveur en interne sur
localhost:3000: le port Nitro doit rester3000(voir §6).
2. Dimensionnement de la VM
RAM
| Poste | Empreinte |
|---|---|
| Serveur Nitro au repos | ~150–300 Mo |
Pic à la génération PDF : Chromium (~300–600 Mo) + traitement d'images sharp en 2× sur des pages A3 + Ghostscript |
1–2 Go ponctuels |
- Recommandé : 4 Go. Plancher : 2 Go (risqué sur des portfolios très riches en images — le processus de génération peut être tué par l'OOM killer).
- La génération est séquentielle (une seule instance Chromium à la fois via une file d'attente) : la RAM ne monte pas linéairement avec le nombre d'utilisateurs.
Disque
Ordres de grandeur mesurés / à prévoir (l'essentiel vit dans data/) :
| Élément | Taille | Note |
|---|---|---|
| Debian + Node + outils | ~3–4 Go | système |
node_modules |
~1–1,5 Go | Nuxt + Playwright + sharp |
| Chromium (Playwright) | ~0,5 Go | navigateur de rendu |
.output (build Nitro) |
~0,1–0,3 Go | régénéré à chaque déploiement |
data/content-cache/ |
~0,75 Go aujourd'hui | miroir de 02.projets (croît avec le contenu) |
data/frozen/ |
~0,4 Go aujourd'hui | médias figés des portfolios générés (croît à l'usage) |
data/exports/ |
~0,015 Go | PDF (1–8 Mo pièce, régénérables) |
data/cache/ |
transitoire | rendu/travail PDF |
data/portfolios/ |
quelques Mo | la donnée métier (JSON) |
- Recommandé : 30 Go (confort + croissance du contenu et des instantanés). Plancher : 20 Go.
CPU
- 2 vCPU recommandés. La génération PDF est CPU-bound (rendu Chromium + ré-échantillonnage d'images + Ghostscript), par à-coups.
3. Prérequis logiciels (installation unique, en root)
Sur la VM portfolios, une seule fois :
apt update && apt install -y curl git rsync openssh-client ghostscript ffmpeg nginx
# Node.js 22 LTS (dépôt NodeSource)
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs
node --version # v22.x
# Dépendances système de Chromium (pour Playwright) — installées plus tard
# par « npx playwright install-deps » (§7), qui nécessite root.
4. Utilisateur et arborescence
On dédie un utilisateur système portfolio qui possède tout (dépôt bare,
checkout, données, service). Cela évite tout jonglage de permissions.
# en root
adduser --disabled-password --gecos "" portfolio
# arborescences
install -d -o portfolio -g portfolio /home/portfolio/gitBareRepos
install -d -o portfolio -g portfolio /var/www/portfolio-generator
install -d -o portfolio -g portfolio /var/lib/portfolio-generator/data
install -d -o portfolio -g portfolio -m 700 /var/lib/portfolio-generator/.ssh
| Chemin | Rôle |
|---|---|
/home/portfolio/gitBareRepos/portfolio-generator.git |
dépôt bare (cible du push) |
/var/www/portfolio-generator/public_html |
checkout de travail (build) |
/var/www/portfolio-generator/deploy.sh |
script de déploiement |
/var/lib/portfolio-generator/data |
données persistantes (NUXT_DATA_DIR) |
/var/lib/portfolio-generator/.ssh/portfolio_sync |
clé privée de synchro rsync |
/etc/portfolio-generator.env |
variables d'environnement du service |
5. Déploiement par push Git
Tout le §5 s'exécute en tant qu'utilisateur
portfolio(sudo -iu portfolio).
5.1 Dépôt bare
cd ~/gitBareRepos
git init --bare portfolio-generator.git
5.2 Hook post-receive
~/gitBareRepos/portfolio-generator.git/hooks/post-receive :
#!/usr/bin/env bash
PRODDIR="/var/www/portfolio-generator"
while read oldrev newrev refname; do
if [ "$refname" = "refs/heads/prod" ]; then
echo "===== DÉPLOIEMENT ====="
unset GIT_DIR
cd "$PRODDIR" || exit 1
. deploy.sh
echo "===== OK ====="
else
echo "Commit non déployé — pushez la branche prod."
fi
done
chmod +x ~/gitBareRepos/portfolio-generator.git/hooks/post-receive
5.3 Checkout de travail
Cloné une fois depuis le dépôt bare (il aura ainsi origin = bare) :
git clone ~/gitBareRepos/portfolio-generator.git /var/www/portfolio-generator/public_html
5.4 Script deploy.sh
/var/www/portfolio-generator/deploy.sh :
#!/usr/bin/env bash
set -euo pipefail
APPDIR="/var/www/portfolio-generator/public_html"
echo "→ mise à jour du code"
cd "$APPDIR"
git fetch origin
git reset --hard origin/prod
echo "→ dépendances + build Nitro"
npm ci
npm run build
echo "→ redémarrage du service"
sudo systemctl restart portfolio-generator
chmod +x /var/www/portfolio-generator/deploy.sh
npm ci télécharge automatiquement Chromium dans le cache de l'utilisateur
portfolio (les dépendances système de Chromium sont, elles, installées une
fois en root à l'étape §7).
5.5 Autoriser le redémarrage du service (sudoers)
deploy.sh a besoin de redémarrer le service ; on limite ce sudo au strict
nécessaire. /etc/sudoers.d/portfolio-generator (en root, visudo -f …) :
portfolio ALL=(root) NOPASSWD: /usr/bin/systemctl restart portfolio-generator
6. Service systemd et environnement
6.1 Fichier d'environnement
/etc/portfolio-generator.env (droits 600, propriétaire portfolio — il
contient des secrets) :
# --- Serveur ---
# Le port DOIT rester 3000 : le rendu PDF appelle le serveur sur localhost:3000.
NITRO_HOST=127.0.0.1
NITRO_PORT=3000
# --- Secrets (valeurs aléatoires longues : openssl rand -hex 32) ---
NUXT_PORTFOLIO_PASSWORD=<mot-de-passe-partagé>
NUXT_SESSION_SECRET=<aléatoire-≥32-caractères>
NUXT_INTERNAL_TOKEN=<aléatoire-≥32-caractères>
# --- Données persistantes (hors du checkout, préservées aux déploiements) ---
NUXT_DATA_DIR=/var/lib/portfolio-generator/data
# --- Source de contenu : production (cache rsync) ---
NUXT_CONTENT_SOURCE=prod
NUXT_SYNC_REMOTE=portfolio-sync@<HOTE_GRAV>:/var/www/figureslibres/user/pages/02.projets/
NUXT_SYNC_SSH_KEY=/var/lib/portfolio-generator/.ssh/portfolio_sync
6.2 Unité systemd
/etc/systemd/system/portfolio-generator.service :
[Unit]
Description=Générateur de portfolios Figures Libres
After=network.target
[Service]
Type=simple
User=portfolio
Group=portfolio
WorkingDirectory=/var/www/portfolio-generator/public_html
EnvironmentFile=/etc/portfolio-generator.env
ExecStart=/usr/bin/node .output/server/index.mjs
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable portfolio-generator # démarrage auto (ne pas encore start : voir §7)
7. Premier déploiement (bootstrap)
-
Côté poste dev, dans le dépôt
generator, ajouter le distant et pousser la brancheprod(déclenche le hook) :git remote add prod portfolio@<VM_PORTFOLIOS>:gitBareRepos/portfolio-generator.git git push prod main:prod # pousse la branche locale main sur la branche prod distanteLe hook lance
deploy.sh→npm ci+npm run build. (Au tout premier push,git reset --hard origin/prodéchoue si le checkout est vide : voir la note.) -
Côté VM (utilisateur
portfolio), une fois le code présent danspublic_html, installer les dépendances système de Chromium (root) :sudo npx --prefix /var/www/portfolio-generator/public_html playwright install-deps chromium -
Démarrer le service :
sudo systemctl start portfolio-generator sudo systemctl status portfolio-generator journalctl -u portfolio-generator -f
Note premier push : si
git reset --hard origin/proddudeploy.shbute sur un checkout vierge, faire une fois manuellement danspublic_html:git fetch origin && git checkout -B prod origin/prod, puis relancer un push.
8. Brancher à la prod Grav (rsync/SSH en lecture seule)
8.1 Clé de synchronisation (VM portfolios, utilisateur portfolio)
ssh-keygen -t ed25519 -N "" \
-f /var/lib/portfolio-generator/.ssh/portfolio_sync -C "portfolio-sync"
cat /var/lib/portfolio-generator/.ssh/portfolio_sync.pub # ← à déposer côté Grav
8.2 Côté VM Grav : accès en lecture seule
Créer un utilisateur dédié et l'autoriser à lire les projets, sans shell ni écriture :
sudo adduser --disabled-password --gecos "" portfolio-sync
sudo setfacl -R -m u:portfolio-sync:rX /var/www/figureslibres/user/pages/02.projets
sudo setfacl -R -d -m u:portfolio-sync:rX /var/www/figureslibres/user/pages/02.projets
Déposer la clé publique dans ~portfolio-sync/.ssh/authorized_keys en la
restreignant à rsync (recommandé) via rrsync :
command="rrsync -ro /var/www/figureslibres/user/pages/02.projets",no-agent-forwarding,no-port-forwarding,no-pty,no-X11-forwarding ssh-ed25519 AAAA...portfolio-sync...
rrsyncaccompagne rsync (/usr/bin/rrsyncou/usr/share/doc/rsync/scripts/rrsync). Il verrouille la clé à unrsyncen lecture seule sur ce seul dossier. À défaut, une clé standard en lecture seule convient. La commande exécutée par l'app est :rsync -az --delete -e "ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes -i <clé>" <remote>/ <cache>/.
8.3 Réseau
- Ouvrir SSH (22) en sortie de la VM portfolios vers la VM Grav.
- Renseigner
NUXT_SYNC_REMOTEavec le chemin absolu de02.projetssur la VM Grav, terminé par/(voir §6.1). - Tester la connexion une fois pour accepter la clé d'hôte :
sudo -u portfolio ssh -i /var/lib/portfolio-generator/.ssh/portfolio_sync \ portfolio-sync@<HOTE_GRAV> "echo ok"
9. Reverse proxy HTTPS (indispensable)
Le HTTPS est requis, pas seulement confortable :
- les liens de partage publics (
/view/<id>) sont faits pour être ouverts hors réseau local ; - la copie du lien dans le presse-papiers n'est autorisée par les navigateurs
qu'en contexte sécurisé (HTTPS ou
localhost) — en HTTP sur IP elle est bloquée.
/etc/nginx/sites-available/portfolio-generator :
server {
listen 80;
server_name portfolios.figureslibres.io;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name portfolios.figureslibres.io;
# ssl_certificate ... ; # via certbot (Let's Encrypt)
# ssl_certificate_key ... ;
client_max_body_size 64m; # médias volumineux
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 600s; # la génération PDF peut être longue
}
}
ln -s /etc/nginx/sites-available/portfolio-generator /etc/nginx/sites-enabled/
apt install -y certbot python3-certbot-nginx
certbot --nginx -d portfolios.figureslibres.io
nginx -t && systemctl reload nginx
Le service Nitro écoutant sur 127.0.0.1:3000, il n'est pas exposé
directement : seul le port 443 est public. Un pare-feu (ufw allow 443,22) durcit
l'ensemble.
10. Première synchronisation & vérification
- Ouvrir
https://portfolios.figureslibres.io. - Se connecter avec
NUXT_PORTFOLIO_PASSWORD. - Cliquer « Rafraîchir » : premier
rsyncvers/var/lib/portfolio-generator/data/content-cache/. Le toast indique le nombre de fichiers et de projets synchronisés. - Créer un portfolio de test et générer un PDF pour valider toute la chaîne (Chromium + Ghostscript).
11. Sauvegardes
L'état applicatif vit dans /var/lib/portfolio-generator/data/ :
| Dossier | Contenu | Sauvegarder ? |
|---|---|---|
portfolios/ |
portfolios (JSON) — la donnée métier | Oui |
frozen/ |
instantanés figés (contenu + composition) | Oui |
exports/ |
PDF générés | Optionnel (régénérables) |
content-cache/ |
miroir rsync de la prod | Non (re-tirable) |
cache/ |
cache de rendu | Non |
Sauvegarde à chaud (les écritures des portfolios sont atomiques) :
tar czf portfolios-backup-$(date +%F).tar.gz \
-C /var/lib/portfolio-generator/data portfolios frozen
12. Mises à jour (au quotidien)
Depuis le poste dev, une fois le travail prêt :
git push prod main:prod
Le hook redéploie tout seul (git reset --hard + npm ci + npm run build +
redémarrage). Les données de /var/lib/portfolio-generator/data/ sont préservées.
Suivre : journalctl -u portfolio-generator -f.
13. Dépannage
| Symptôme | Piste |
|---|---|
| Le push ne déploie pas | Vérifier qu'on pousse bien vers la branche prod (git push prod main:prod) et que post-receive est exécutable. |
deploy.sh échoue sur systemctl restart |
Vérifier l'entrée sudoers.d/portfolio-generator (§5.5). |
| Service qui ne démarre pas | journalctl -u portfolio-generator -e ; souvent .output absent (build échoué) ou EnvironmentFile manquant. |
| Génération PDF en échec | Dépendances Chromium manquantes → npx playwright install-deps chromium (§7.2) ; sinon vérifier RAM (OOM) dans journalctl. |
| « Rafraîchir » échoue (« Échec rsync ») | Tester la commande SSH (§8.3) ; vérifier NUXT_SYNC_REMOTE (chemin absolu, / final), les droits de lecture et le pare-feu sortant (22). |
| Copie du lien public impossible | Accès en HTTP/IP → passer par l'URL HTTPS (§9). |
| PDF « à jour » ne se met pas à jour | La régénération réutilise l'instantané figé ; utiliser « Réactualiser depuis Grav » pour resynchroniser le contenu. |
14. Récapitulatif express
# ── VM portfolios (root, une fois) ──
apt update && apt install -y curl git rsync openssh-client ghostscript ffmpeg nginx
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && apt install -y nodejs
adduser --disabled-password --gecos "" portfolio
install -d -o portfolio -g portfolio /home/portfolio/gitBareRepos /var/www/portfolio-generator
install -d -o portfolio -g portfolio /var/lib/portfolio-generator/data
install -d -o portfolio -g portfolio -m 700 /var/lib/portfolio-generator/.ssh
# → créer bare repo + hook + deploy.sh (§5), env + service systemd (§6),
# sudoers (§5.5), clé de synchro (§8.1)
# ── Poste dev ──
git remote add prod portfolio@<VM>:gitBareRepos/portfolio-generator.git
git push prod main:prod
# ── VM portfolios (après 1er push) ──
sudo npx --prefix /var/www/portfolio-generator/public_html playwright install-deps chromium
sudo systemctl start portfolio-generator
# ── Côté Grav : déposer la clé publique en lecture seule (§8.2) ──
# ── nginx + certbot en façade (§9), puis « Rafraîchir » dans l'UI ──