Déployer chez un hébergeur client
Cette procédure concerne les sites développés sur le VPS Levell mais hébergés
chez le client — le cas d’onas.sn chez Infomaniak. Pour les applications qui
tournent sur notre propre VPS, voir plutôt
Déployer une nouvelle application.
1. Le principe
Section intitulée « 1. Le principe »Trois idées, dont découle tout le reste.
L’artefact est autonome. Next.js sait produire une sortie standalone :
un serveur complet accompagné des seules dépendances réellement utilisées.
Compilé dans une image dont la bibliothèque système correspond à celle du
serveur cible, il n’exige aucun npm install chez le client — donc aucun
shell, aucun pic de mémoire, aucune dépendance de développement manquante.
Les données du client sont hors d’atteinte, par construction. La base, les
médias du back-office et le fichier .env sont déclarés dans une liste
PROTECTED. Ce n’est pas une exclusion de précaution : l’archive est relue
avant tout téléversement et le script s’interrompt si l’un de ces chemins s’y
trouve.
La bascule est réversible. L’ancien .next est déplacé dans une sauvegarde
horodatée, le nouveau prend sa place par un simple mv — instantané, puisqu’on
reste sur le même système de fichiers. Le retour arrière refait l’opération en
sens inverse.
2. Le kit
Section intitulée « 2. Le kit »/home/sidy/deploy-kit/├── deploy.sh # le code├── migrate-db.sh # les changements de schéma├── lib/remote.sh # transport (scp + ssh -tt)├── clients/<nom>.conf # configuration, versionnée├── clients/<nom>.secret # mot de passe, chmod 600, JAMAIS versionné├── artifacts/ # artefacts compilés└── backups/ # bases rapatriéesCe que contient une configuration client
Section intitulée « Ce que contient une configuration client »CLIENT_NAME="ONAS"SOURCE_REPO="/docker/onas-web"
REMOTE_HOST="<hôte-ssh-du-client>"REMOTE_USER="<utilisateur-ssh>"REMOTE_APP="/srv/customer/web" # chemin figé par le panneau de l'hébergeurREMOTE_WORK="/srv/customer/.deploy" # sauvegardes + transit
BUILD_IMAGE="node:24-bookworm-slim" # DOIT correspondre à la glibc du serveur cible
PROTECTED=( "data" "public/media" ".env" ) # garde-fou — ne jamais viderSTABLE=( "public/images" ) # lourd et stable, envoyé sur --with-images
HEALTH_BASE="https://onas.sn"RESTART_MODE="manual"3. Avant le premier déploiement : le relevé
Section intitulée « 3. Avant le premier déploiement : le relevé »Ne jamais s’engager sur un hébergement qu’on n’a pas mesuré. Le relevé se fait en lecture seule et prend cinq minutes :
cat /etc/os-release | head -2 # distributionldd --version | head -1 # ← LA donnée qui conditionne l'image de buildnode -v ; npm -vfind / -maxdepth 6 -name "next.config.*" -not -path "*/node_modules/*" 2>/dev/nulldf -h . ; free -m ; nprocfor t in rsync git tar curl unzip; do command -v $t; doneLa version de glibc détermine BUILD_IMAGE. Un artefact compilé sur une
glibc plus récente que celle du serveur ne démarrera pas : les binaires
natifs (sharp, @libsql/client) sont incompatibles.
| Serveur cible | BUILD_IMAGE |
|---|---|
| Debian 12 « bookworm », glibc 2.36 | node:24-bookworm-slim |
| Debian 11 « bullseye », glibc 2.31 | node:24-bullseye-slim |
| Alpine, musl | node:24-alpine |
4. Déployer du code
Section intitulée « 4. Déployer du code »cd /home/sidy/deploy-kit./deploy.sh <client>Le script enchaîne : compilation de l’artefact → assemblage de l’archive → vérification que l’archive ne contient aucune donnée client → contrôles préalables sur le serveur → téléversement → sauvegarde → bascule → contrôle de santé.
| Option | Effet |
|---|---|
--dry-run | Tout sauf le téléversement. À utiliser sans hésiter. |
--no-build | Réutilise le dernier artefact compilé. |
--with-images | Envoie aussi public/images. Obligatoire si les images ont changé. |
--health-only | Ne fait que le contrôle de santé. |
--rollback | Restaure une sauvegarde antérieure. |
--no-health | S’arrête après la mise en place (le contrôle n’a de sens qu’après redémarrage). |
--yes | Sans confirmation. |
Le redémarrage
Section intitulée « Le redémarrage »Sur un hébergement mutualisé de type « site Node.js », la console SSH tourne
dans un conteneur distinct de celui qui exécute l’application — les deux ne
partagent que le disque. Aucun processus applicatif n’y est visible, ni pm2,
ni systemctl : le redémarrage passe obligatoirement par le panneau de
l’hébergeur.
Une astuce évite d’avoir à faire reconfigurer quoi que ce soit : l’artefact
embarque "start": "node server.js" dans son package.json. Le panneau
continue de lancer npm start, sans savoir que le contenu a changé.
./deploy.sh <client> --no-health # met en place, s'arrête proprement# → Panneau de l'hébergeur → Node.js → Redémarrer./deploy.sh <client> --health-only # vérifie5. Changer le schéma de la base
Section intitulée « 5. Changer le schéma de la base »L’artefact autonome n’embarque ni src/migrations/ ni tsx : la migration ne
peut pas s’exécuter chez le client. On rapatrie sa base, on la migre ici, on
la renvoie.
./migrate-db.sh <client> --dry-run # répétition sur ses données réelles./migrate-db.sh <client> # pour de vraiLes quatre garanties
Section intitulée « Les quatre garanties »- La base d’origine est sauvegardée avant toute opération, horodatée, en
lecture seule (
chmod 444), et jamais écrasée par une exécution ultérieure. - La migration s’applique sur une copie.
- Le nombre de lignes de chaque table est compté avant et après. La moindre perte interrompt tout — le renvoi n’a pas lieu.
- Une sauvegarde est également déposée sur le serveur du client avant le remplacement.
Toujours migrer avant de déployer. Le code lit les nouvelles tables mais retombe sur un contenu de repli si elles sont absentes — l’inverse (migrer sans déployer encore) est également sans danger : l’ancien code ignore simplement les tables qu’il ne connaît pas.
6. Retour arrière
Section intitulée « 6. Retour arrière »./deploy.sh <client> --rollback # liste les sauvegardes, en restaure unePuis redémarrer. Pour la base, la sauvegarde est sur le serveur du client :
cp /srv/customer/.deploy/backups/db/payload-<horodatage>.db \ /srv/customer/web/data/payload.db7. Particularités des hébergements mutualisés
Section intitulée « 7. Particularités des hébergements mutualisés »Ces contraintes ont été mesurées sur Infomaniak, mais se retrouvent ailleurs.
| Contrainte | Conséquence |
|---|---|
Canal SSH exec refusé | ssh hôte 'commande' sort en 255 sans rien renvoyer. ssh -tt fonctionne : on téléverse le script, puis on ne tape qu’une ligne (bash script) — c’est ce qui rend $? exploitable, car dans un pseudo-terminal $? après plusieurs lignes ne désigne que la dernière. |
| Sous-système SFTP refusé | « Permission denied » quel que soit le mode d’authentification. Seul scp passe. |
rsync inutilisable | Il a besoin d’exec. Tout passe donc par une archive unique en scp. |
| Authentification par clé indisponible | Infomaniak l’annonce dans sa bannière (« will be supported soon »). Mot de passe obligatoire, à faire renouveler régulièrement. |
| Redémarrage non scriptable | Conteneur distinct — panneau obligatoire. |
| Bannière au login | Elle avale l’entrée envoyée trop tôt : temporiser avant d’écrire dans le pseudo-terminal. |
8. Pièges vérifiés — et comment les reconnaître
Section intitulée « 8. Pièges vérifiés — et comment les reconnaître »| Symptôme | Cause | Correction |
|---|---|---|
Failed query: CREATE INDEX …_idx pendant la migration | NODE_ENV=development déclenche une synchronisation automatique de schéma avant la migration, qui tente de recréer des index existants | NODE_ENV=production |
sh: 1: next: not found | Artefact non autonome, node_modules absent | Compiler avec output: 'standalone' |
| Le site sert encore l’ancienne version | Application non redémarrée | Panneau → Redémarrer |
| Toutes les images repassent lentement après chaque mise en ligne | .next/cache/images détruit par la bascule | deploy.sh reporte ce cache — vérifier la ligne « cache d’images conservé » |
Module not found sur src/data/… à la compilation | Motif rsync non ancré : --exclude data exclut aussi src/data/ | Ancrer : --exclude /data |
| « ancien build encore servi » alors que tout est correct | Avec Turbopack, les chunks sont nommés par empreinte de contenu : le BUILD_ID n’y figure pas | Interroger /_next/static/<BUILD_ID>/_buildManifest.js (200 = en ligne) |
Vérifier quel build est réellement servi
Section intitulée « Vérifier quel build est réellement servi »Deux méthodes indépendantes, à privilégier sur toute déduction :
# 1. Le manifeste du build attendu répond-il ?curl -s -o /dev/null -w '%{http_code}\n' \ https://<site>/_next/static/<BUILD_ID>/_buildManifest.js # 200 attendu
# 2. Identité d'octets d'un chunk serviC=$(curl -s https://<site>/ | grep -oE '/_next/static/chunks/[A-Za-z0-9_.~-]+\.js' | head -1)curl -s "https://<site>$C" | md5summd5sum /home/sidy/deploy-kit/artifacts/<client>/build/.next${C#/_next}9. Cas de référence : onas.sn
Section intitulée « 9. Cas de référence : onas.sn »Déploiement du 13 août 2026, BUILD_ID 8sv-VvFvWXYTTg1niNi9H.
| Avant | Après | |
|---|---|---|
node_modules sur le serveur | 818 Mo | 53 Mo |
public/images | 277 Mo | 82 Mo |
| Archive transférée | 1,27 Go (livraison manuelle) | 108 Mo, 3 s |
| Images de la page d’accueil | 166 variantes, 15,27 Mo | 152 variantes, 9,33 Mo |
npm install chez le client | obligatoire | aucun |
Base, médias et .env vérifiés intacts après bascule. Migration de schéma
appliquée sans perdre une ligne sur 34 tables.
Séquence exécutée :
cd /home/sidy/deploy-kit./migrate-db.sh onas # ~1 min./deploy.sh onas --with-images --no-health # 3 s de transfert# → Manager Infomaniak → Node.js → Redémarrer./deploy.sh onas --health-only # tout au vert