Aller au contenu
Q
QuoteNode

Installation

Migrer les données vers PostgreSQL 18

Procédure unique et sûre pour les données afin de faire passer un déploiement PostgreSQL 16 existant vers PostgreSQL 18 sans perte de données.

Migrer les données vers PostgreSQL 18

Pour qui : uniquement les déploiements qui tournent déjà sur PostgreSQL 16 et veulent passer à une version de QuoteNode qui exige PostgreSQL 18. Les nouvelles installations n’ont besoin de rien — elles démarrent directement sur PostgreSQL 18.

Un serveur PostgreSQL 18 ne peut pas démarrer sur un répertoire de données PostgreSQL 16. L’application refuse de démarrer sur un répertoire incompatible et ne le réécrit jamais automatiquement ; une installation existante doit donc faire avancer ses données une fois. C’est une procédure d’opérateur délibérée et unique.

La migration est sûre pour les données par conception : votre volume de données existant (appelons-le A) n’est jamais supprimé, recréé ou écrasé. Les données sont copiées dans un nouveau volume PostgreSQL 18 (B) ; A reste intact comme rollback immédiat jusqu’à ce que vous confirmiez que B fonctionne.

Il y a trois chemins (le chemin C est un dernier recours déconseillé). Choisissez selon que vous avez un shell sur l’hôte :

CheminQuand l’utiliserMécanisme
A — CLI de l’hôteVous pouvez exécuter docker sur l’hôte (Ubuntu VPS, LAN Linux, Compose simple)scripts/migrate-postgres-major.sh
B — stack UI uniquementVous n’avez qu’une UI (Coolify, Portainer, QNAP, Synology)infra/docker-compose.upgrade.yml
C — auto-mise à niveau sur place ⚠️Dernier recours uniquement ; ni A ni B ne fonctionne et vous acceptez le risque sur placeimage pgautoupgrade (non recommandé)

Les chemins A et B sont sûrs pour les données : ils copient vers un nouveau volume et laissent l’original intact. Le chemin C met à niveau sur place — il n’y a pas de rollback intact, seulement votre sauvegarde hors hôte. Préférez A ou B.


Avant de commencer (tous les chemins)

  1. Sauvegardez .env. Il contient des secrets non régénérables, surtout DB_ENCRYPTION_KEY.
  2. Faites une sauvegarde de la base (en plus de la copie de sécurité automatique de la migration) :
    docker compose exec postgres pg_dump -U quotenode quotenode > backup-before-pg18.sql
  3. Mettez en pause l’auto-déploiement si votre plateforme redéploie sur un tag mobile (p. ex. auto-déploiement Coolify, :latest Portainer). Voir Ordre de publication — une image PG18 ne doit pas arriver avant l’exécution de la migration.
  4. Arrêtez la stack applicative (sans supprimer les volumes). N’exécutez jamais docker compose down -v.

Chemin A — CLI de l’hôte (recommandé si vous avez un shell)

C’est le chemin le plus simple pour Ubuntu VPS, LAN Linux et tout hôte Docker Compose simple.

  1. Trouvez le nom du volume de données :
    docker volume ls | grep postgres
  2. Arrêtez la stack (gardez les volumes) :
    docker compose down
  3. Lancez la migration. Elle capture deux artefacts de sécurité (un dump logique et un tarball brut du volume) et les vérifie tous deux avant de toucher quoi que ce soit, exécute un preflight fail-closed sur le cluster en cours, puis recrée le volume sur PostgreSQL 18 et restaure :
    DB_NAME=quotenode DB_USERNAME=quotenode DB_PASSWORD='...' \
    POSTGRES_VOLUME=<your_postgres_volume> \
    DB_MAJOR_MIGRATE_CONFIRM=yes \
      ./scripts/migrate-postgres-major.sh
    Le script ne fait rien de destructif sans DB_MAJOR_MIGRATE_CONFIRM=yes et s’interrompt (vos données restant intactes) s’il trouve des rôles/extensions inattendus ou un espace disque insuffisant.
  4. Démarrez la stack normalement. Elle remonte sur PostgreSQL 18 et applique les migrations en attente :
    docker compose up -d
  5. Faites la Vérification, puis supprimez les artefacts de sécurité affichés par le script une fois rassuré.

Locale non par défaut : si votre base source utilise une locale différente de celle de l’image par défaut, le script échoue en fail-closed sur la vérification encodage/collation (un changement silencieux de collation peut corrompre les index texte). Relancez avec un POSTGRES_INITDB_ARGS correspondant (p. ex. --locale=...).

Remarques pour Coolify (hôtes gérés)

Le chemin A exécuté via SSH sur le serveur Coolify est la route la plus simple sur un hôte Coolify. Quelques spécificités de Coolify, confirmées en pratique :

  • Un déploiement « Failed » avant la migration est attendu. Quand Coolify déploie une version qui exige PostgreSQL 18 sur un volume PostgreSQL 16 existant, le conteneur postgres boucle en crash (FATAL: database files are incompatible with server) et Coolify marque le déploiement comme Failed. Vos données ne sont pas touchées — PostgreSQL refuse simplement de démarrer sur un répertoire de données plus ancien.
  • Vous devez supprimer le conteneur postgres pour migrer. Le chemin A recrée le volume de données, et Docker refuse de supprimer un volume tant qu’un conteneur — même arrêté — le référence encore. La procédure supprime donc d’abord le conteneur postgres de Coolify ; l’arrêter ne suffit pas.
  • Une fois la base migrée, relancez la stack avec « Redeploy » — pas « Restart ». Comme le conteneur postgres a été supprimé, Coolify doit le recréer. Cliquez sur Redeploy sur la ressource ; Coolify démarre alors postgres:18-alpine sur le volume migré et remonte l’application.
  • Coolify réutilise le volume migré. Recréer le volume supprime les labels Compose de Coolify, mais Docker Compose (et donc le redeploy de Coolify) apparie les volumes par nom et réutilise l’existant — vos données restent. Conservez les artefacts de sécurité jusqu’à avoir vérifié l’application redéployée.
  • Trouvez le vrai nom du volume dans l’onglet Storages de la ressource ; il a un préfixe UUID, p. ex. <uuid>_postgres-prod-data (notez les tirets). Si vous faites tourner plusieurs stacks (p. ex. une install dev et une main) sur un hôte, migrez chacune par son propre nom de volume et ne touchez pas l’autre.

Chemin B — stack de migration UI uniquement (Coolify, Portainer, NAS)

Quand vous n’avez pas de shell sur l’hôte, utilisez la stack de migration dédiée et temporaire infra/docker-compose.upgrade.yml. Elle ne fait partie d’aucun déploiement normal — montez-la uniquement pour la migration et supprimez-la ensuite. Elle utilise des images PostgreSQL standard, sans socket Docker, et est entièrement pilotée par les entrypoints des conteneurs.

Elle exécute ces phases automatiquement et s’arrête à un rapport vérifié — elle ne promeut jamais d’elle-même :

  1. cold-backup — un tar immuable du volume A (image de rollback), pris avant toute lecture de A.
  2. dump — un preflight fail-closed du cluster en cours + un dump logique.
  3. restore — restauration dans un nouveau volume PostgreSQL 18 B.
  4. verify — vérifie la version majeure, le schéma, les extensions et la parité de locale, et écrit REPORT.txt dans le volume d’artefacts.

Étapes

  1. Arrêtez la stack applicative dans l’UI de votre plateforme (ne supprimez pas les volumes).

  2. Trouvez le nom du volume de données existant (c’est A, utilisé comme SOURCE_VOLUME_NAME) :

    • Coolify : le volume est préfixé par l’UUID de la ressource, p. ex. <uuid>_postgres_prod_data.
    • Portainer / QNAP / Synology : le préfixe est le nom de la stack, p. ex. <stack>_postgres_data.
    • Défaut Compose : <project>_postgres_prod_data.
  3. Déployez la stack de migration avec ces variables d’environnement :

    VariableValeur
    SOURCE_VOLUME_NAMEle volume existant A de l’étape 2
    DB_NAME DB_USERNAME DB_PASSWORDvos identifiants de base existants
    TARGET_VOLUME_NAMEoptionnel ; défaut quotenode_postgres_upgraded (c’est B)
    # équivalent Docker pur de ce que lance l'UI :
    docker compose -f infra/docker-compose.upgrade.yml up
  4. Lisez le rapport. Quand la phase verify se termine, lisez REPORT.txt depuis le volume d’artefacts (quotenode_pg_upgrade_artifacts). Il doit indiquer PASS. Si une phase échoue, la stack s’arrête en sécurité ; A est intact — corrigez la cause et réessayez (supprimez B d’abord, voir Récupération).

  5. Supprimez la stack de migration (gardez ses volumes), puis Promouvoir B.

Visibilité des volumes selon la plateforme (à vérifier chez vous) : le chemin B suppose que la stack de migration peut monter le volume A existant de l’application comme volume external par nom. La plupart des hôtes Docker le permettent. Si votre plateforme isole les volumes par stack/ressource et que la stack de migration ne voit pas A, utilisez la variante sur place : remplacez temporairement le compose de l’application par docker-compose.upgrade.yml dans la même ressource/projet afin qu’elle hérite du même namespace de volumes. Confirmez-le sur votre plateforme précise avant de vous y fier en production.


Chemin C — image d’auto-mise à niveau sur place (le plus simple en apparence, mais risqué — non recommandé) ⚠️

Dernier recours uniquement. Utilisez-le seulement quand ni le chemin A ni le chemin B n’est faisable et que vous acceptez les risques ci-dessous. Contrairement à A et B, ce chemin met à niveau sur place sur votre volume A existant, donc il n’y a pas de volume de rollback intact — votre sauvegarde hors hôte est le seul filet de sécurité.

L’idée semble la plus simple : laissez une image auto-mise à niveau (pgautoupgrade) exécuter pg_upgrade une fois sur votre volume existant, puis démarrez le postgres:18-alpine normal dessus. Mais ce n’est pas un changement de tag en une ligne dans votre compose normal — le service postgres y a un override command: (qui supprime entièrement l’entrypoint de mise à niveau), restart: unless-stopped (qui boucle le one-shot terminé) et un healthcheck (qui marque le serveur en cours de mise à niveau comme unhealthy). Les trois cassent une mise à niveau sur place.

Le repo fournit un fichier one-shot prêt et autonome qui les évite tous : infra/docker-compose.pgautoupgrade.unsafe.yml (le .unsafe est délibéré). Faits clés :

  • pg_upgrade est destructif sur place — en cas de succès l’ancien cluster disparaît, et un échec à mi-chemin peut laisser le répertoire de données à moitié converti. Votre sauvegarde hors hôte est le seul rollback.
  • Le fichier conserve PGAUTO_ONESHOT=yes, le PGDATA=/var/lib/postgresql/data legacy, pas de healthcheck et restart: no, et épingle l’image par digest.

Étapes

  1. Arrêtez la stack et faites une copie brute hors hôte du volume A — c’est votre seul rollback :
    docker run --rm -v <your_postgres_volume>:/d:ro -v "$PWD:/out" alpine:3.23 \
      tar czf /out/pg16-volume-backup.tar.gz -C /d .
    Copiez pg16-volume-backup.tar.gz hors de l’hôte.
  2. Lancez le fichier one-shot prêt contre votre volume en cours (SOURCE_VOLUME_NAME = volume A) :
    SOURCE_VOLUME_NAME=<your_postgres_volume> \
    DB_NAME=quotenode DB_USERNAME=quotenode DB_PASSWORD='...' \
      docker compose -f infra/docker-compose.pgautoupgrade.unsafe.yml up
    Attendez que le conteneur se termine avec exit 0.
  3. Vérifiez la disposition — le volume doit maintenant être PostgreSQL 18 sur le chemin legacy :
    docker run --rm -v <your_postgres_volume>:/d:ro alpine:3.23 cat /d/PG_VERSION   # -> 18
  4. Démarrez votre stack normale (postgres:18-alpine). Elle utilise le même volume, maintenant mis à niveau — aucune étape de promotion nécessaire (la mise à niveau était sur place).
  5. Faites la Vérification. Si quelque chose ne va pas, restaurez A depuis pg16-volume-backup.tar.gz.

Plateformes UI uniquement (Coolify/Portainer) : collez le contenu de infra/docker-compose.pgautoupgrade.unsafe.yml dans une nouvelle stack temporaire, définissez les variables ci-dessus, déployez une fois, confirmez la sortie, supprimez cette stack, puis pointez votre stack normale vers le même volume.


Promouvoir B

Après vérification, pointez la stack principale vers le volume migré B. Les fichiers compose de base ne sont jamais modifiés (pour que les nouvelles installations restent intactes) ; la promotion est un override.

  • Docker / Compose pur : ajoutez l’override de promotion et définissez POSTGRES_VOLUME_NAME sur B :
    POSTGRES_VOLUME_NAME=quotenode_postgres_upgraded \
      docker compose -f infra/docker-compose.coolify.yml \
                     -f infra/docker-compose.promote.yml up -d
    (utilisez docker-compose.prod.yml au lieu de coolify.yml pour le fichier prod). Pour revenir en arrière, retirez -f docker-compose.promote.yml et redémarrez — vous êtes de retour sur A.
  • UI Coolify / Portainer : il n’y a pas de chaînage -f dans l’UI. Remappez plutôt le stockage du service postgres vers le volume B dans les réglages de stockage de la plateforme, ou ajoutez external: true + name: <B> pour le volume postgres dans l’éditeur compose de la stack.

Vérifier

Une fois la stack principale en marche sur B :

  1. Ouvrez /health — doit renvoyer UP.
  2. Connectez-vous et confirmez que vos données existantes (devis, clients, paramètres) sont présentes.
  3. Créez un devis de test et générez un PDF.
  4. Ouvrez un lien public pour confirmer le routage.

Ce n’est qu’après cela que vous devez supprimer le volume A et les artefacts de migration.


Ordre de publication (important)

Si votre plateforme déploie automatiquement sur un tag mobile, une image exigeant PG18 pourrait arriver avant votre migration et casser la stack. Toujours :

  1. Mettez en pause l’auto-déploiement / épinglez le tag d’image actuel.
  2. Exécutez la migration (chemin A ou B) et promouvez B.
  3. Ensuite seulement mettez à niveau l’application vers la version PostgreSQL 18 et réactivez l’auto-déploiement.

Récupération

Le volume source A n’est jamais modifié, donc la récupération est simple :

  • Migration interrompue avant la promotion (timeout, redeploy, arrêt) : supprimez le volume B et le volume d’artefacts, puis recommencez. Il n’y a pas d’état partiel à reprendre.
  • Problème après la promotion : annulez la promotion (retirez l’override / videz POSTGRES_VOLUME_NAME ou repointez le stockage vers A) et démarrez la stack — vous êtes de retour sur PostgreSQL 16 avec toutes les données.
  • Dernier recours : restaurez depuis backup-before-pg18.sql (voir Mettre à jour QuoteNode → Revenir en arrière).

N’exécutez jamais docker compose down -v pendant cette procédure — cela supprime les volumes.

Last reviewed: Recently