Vai al contenuto
Q
QuoteNode

Installation

Migrare i dati a PostgreSQL 18

Procedura una tantum e sicura per i dati per spostare un deployment PostgreSQL 16 esistente a PostgreSQL 18 senza perdere dati.

Migrare i dati a PostgreSQL 18

A chi serve: solo ai deployment che girano già su PostgreSQL 16 e vogliono passare a una build di QuoteNode che richiede PostgreSQL 18. Le installazioni nuove non hanno bisogno di nulla — partono direttamente su PostgreSQL 18.

Un server PostgreSQL 18 non può avviarsi su una directory dati PostgreSQL 16. L’applicazione rifiuta di avviarsi su una directory incompatibile e non la riscrive mai automaticamente, quindi un’installazione esistente deve far avanzare i suoi dati una volta. È una procedura d’operatore deliberata e una tantum.

La migrazione è sicura per i dati per progettazione: il tuo volume dati esistente (chiamiamolo A) non viene mai cancellato, ricreato o sovrascritto. I dati vengono copiati in un nuovo volume PostgreSQL 18 (B); A resta intatto come rollback immediato finché non confermi che B funziona.

Ci sono tre percorsi (il percorso C è un’ultima risorsa sconsigliata). Scegli in base al fatto che tu abbia o meno una shell sull’host:

PercorsoQuando usarloMeccanismo
A — CLI dell’hostPuoi eseguire docker sull’host (Ubuntu VPS, LAN Linux, Compose semplice)scripts/migrate-postgres-major.sh
B — stack solo-UIHai solo una UI (Coolify, Portainer, QNAP, Synology)infra/docker-compose.upgrade.yml
C — auto-upgrade in place ⚠️Solo ultima risorsa; né A né B funziona e accetti il rischio in placeimmagine pgautoupgrade (sconsigliato)

I percorsi A e B sono sicuri per i dati: copiano in un nuovo volume e lasciano l’originale intatto. Il percorso C aggiorna in place — non c’è un rollback intatto, solo il tuo backup fuori host. Preferisci A o B.


Prima di iniziare (tutti i percorsi)

  1. Salva .env. Contiene segreti non rigenerabili, in particolare DB_ENCRYPTION_KEY.
  2. Fai un backup del database (oltre alla copia di sicurezza automatica fatta dalla migrazione):
    docker compose exec postgres pg_dump -U quotenode quotenode > backup-before-pg18.sql
  3. Metti in pausa l’auto-deploy se la tua piattaforma ridistribuisce su un tag mobile (es. auto-deploy di Coolify, :latest di Portainer). Vedi Ordine di rilascio — un’immagine PG18 non deve arrivare prima dell’esecuzione della migrazione.
  4. Ferma lo stack dell’applicazione (senza cancellare i volumi). Non eseguire mai docker compose down -v.

Percorso A — CLI dell’host (consigliato se hai una shell)

È il percorso più semplice per Ubuntu VPS, LAN Linux e qualunque host Docker Compose semplice.

  1. Trova il nome del volume dati:
    docker volume ls | grep postgres
  2. Ferma lo stack (mantieni i volumi):
    docker compose down
  3. Esegui la migrazione. Cattura due artefatti di sicurezza (un dump logico e un tarball grezzo del volume) e li verifica entrambi prima di toccare qualunque cosa, esegue un preflight fail-closed sul cluster in esecuzione, poi ricrea il volume su PostgreSQL 18 e ripristina:
    DB_NAME=quotenode DB_USERNAME=quotenode DB_PASSWORD='...' \
    POSTGRES_VOLUME=<your_postgres_volume> \
    DB_MAJOR_MIGRATE_CONFIRM=yes \
      ./scripts/migrate-postgres-major.sh
    Lo script non fa nulla di distruttivo senza DB_MAJOR_MIGRATE_CONFIRM=yes e si interrompe (lasciando i tuoi dati intatti) se trova ruoli/extension inattesi o spazio su disco insufficiente.
  4. Avvia lo stack normalmente. Salirà su PostgreSQL 18 e applicherà le migrazioni in sospeso:
    docker compose up -d
  5. Esegui la Verifica, poi cancella gli artefatti di sicurezza stampati dallo script quando sei sicuro.

Locale non predefinito: se il tuo database di origine usa un locale diverso da quello predefinito dell’immagine, lo script si interrompe in fail-closed sul controllo encoding/collation (un cambio silenzioso di collation può corrompere gli indici testuali). Riesegui con un POSTGRES_INITDB_ARGS corrispondente (es. --locale=...).

Note per Coolify (host gestiti)

Il percorso A eseguito via SSH sul server Coolify è la via più semplice su un host Coolify. Alcune specificità di Coolify, confermate nella pratica:

  • Un deployment «Failed» prima di migrare è previsto. Quando Coolify distribuisce una build che richiede PostgreSQL 18 su un volume PostgreSQL 16 esistente, il container postgres va in crash-loop (FATAL: database files are incompatible with server) e Coolify segna il deployment come Failed. I tuoi dati non vengono toccati — PostgreSQL si rifiuta semplicemente di avviarsi su una directory dati più vecchia.
  • Devi rimuovere il container postgres per migrare. Il percorso A ricrea il volume dati, e Docker si rifiuta di rimuovere un volume finché un qualunque container — anche fermo — lo referenzia ancora. Perciò la procedura rimuove prima il container postgres di Coolify; fermarlo non basta.
  • Dopo aver migrato il database, riporta su lo stack con «Redeploy» — non «Restart». Poiché il container postgres è stato rimosso, Coolify deve ricrearlo. Clicca Redeploy sulla risorsa; Coolify avvia allora postgres:18-alpine sul volume migrato e riporta su l’app.
  • Coolify riusa il volume migrato. Ricreare il volume perde le label Compose di Coolify, ma Docker Compose (e quindi il redeploy di Coolify) abbina i volumi per nome e riusa quello esistente — i tuoi dati restano. Conserva gli artefatti di sicurezza finché non hai verificato l’app ridistribuita.
  • Trova il nome reale del volume nella scheda Storages della risorsa; ha un prefisso UUID, es. <uuid>_postgres-prod-data (nota i trattini). Se gestisci più stack (es. un’installazione dev e una main) su un host, migra ciascuno per il suo nome di volume e non toccare l’altro.

Percorso B — stack di migrazione solo-UI (Coolify, Portainer, NAS)

Quando non hai una shell sull’host, usa lo stack di migrazione dedicato e temporaneo infra/docker-compose.upgrade.yml. Non fa parte di alcun deployment normale — tiralo su solo per la migrazione e rimuovilo dopo. Usa immagini PostgreSQL standard, nessun socket Docker, ed è guidato interamente dagli entrypoint dei container.

Esegue queste fasi automaticamente e si ferma a un report verificato — non promuove mai da solo:

  1. cold-backup — un tar immutabile del volume A (immagine di rollback), preso prima che qualcosa legga A.
  2. dump — un preflight fail-closed del cluster in esecuzione + un dump logico.
  3. restore — ripristino in un nuovo volume PostgreSQL 18 B.
  4. verify — controlla versione maggiore, schema, extension e parità di locale, e scrive REPORT.txt nel volume degli artefatti.

Passaggi

  1. Ferma lo stack dell’applicazione nella UI della tua piattaforma (non cancellare i volumi).

  2. Trova il nome del volume dati esistenteA, usato come SOURCE_VOLUME_NAME):

    • Coolify: il volume ha il prefisso dell’UUID della risorsa, es. <uuid>_postgres_prod_data.
    • Portainer / QNAP / Synology: il prefisso è il nome dello stack, es. <stack>_postgres_data.
    • Default di Compose: <project>_postgres_prod_data.
  3. Distribuisci lo stack di migrazione con queste variabili d’ambiente:

    VariabileValore
    SOURCE_VOLUME_NAMEil volume esistente A del passaggio 2
    DB_NAME DB_USERNAME DB_PASSWORDle tue credenziali di database esistenti
    TARGET_VOLUME_NAMEopzionale; default quotenode_postgres_upgraded (questo è B)
    # equivalente Docker puro di ciò che esegue la UI:
    docker compose -f infra/docker-compose.upgrade.yml up
  4. Leggi il report. Quando la fase verify finisce, leggi REPORT.txt dal volume degli artefatti (quotenode_pg_upgrade_artifacts). Deve dire PASS. Se una fase fallisce, lo stack si ferma in modo sicuro; A è intatto — correggi la causa e riprova (cancella prima B, vedi Ripristino).

  5. Rimuovi lo stack di migrazione (mantieni i suoi volumi), poi Promuovere B.

Visibilità dei volumi per piattaforma (verifica sulla tua): il percorso B presuppone che lo stack di migrazione possa montare il volume A esistente dell’applicazione come volume external per nome. La maggior parte degli host Docker lo consente. Se la tua piattaforma isola i volumi per stack/risorsa e lo stack di migrazione non vede A, usa la variante in place: sostituisci temporaneamente il compose dell’applicazione con docker-compose.upgrade.yml nella stessa risorsa/progetto così da ereditare lo stesso namespace dei volumi. Confermalo sulla tua piattaforma specifica prima di affidartici in produzione.


Percorso C — immagine di auto-upgrade in place (sembra il più semplice, ma rischioso — sconsigliato) ⚠️

Solo ultima risorsa. Usalo solo quando né il percorso A né il B è praticabile e accetti i rischi sotto. A differenza di A e B, questo percorso aggiorna in place sul tuo volume A esistente, quindi non c’è un volume di rollback intatto — il tuo backup fuori host è l’unica rete di sicurezza.

L’idea sembra la più semplice: lascia che un’immagine auto-aggiornante (pgautoupgrade) esegua pg_upgrade una volta sul tuo volume esistente, poi avvia il normale postgres:18-alpine su di esso. Ma non è un cambio di tag su una riga nel tuo compose normale — il servizio postgres lì ha un override command: (che sopprime del tutto l’entrypoint di upgrade), restart: unless-stopped (che manderebbe in loop il one-shot terminato) e un healthcheck (che segna il server in upgrade come unhealthy). Tutti e tre rompono un upgrade in place.

Il repo include un file one-shot pronto e autonomo che li evita tutti: infra/docker-compose.pgautoupgrade.unsafe.yml (il .unsafe è voluto). Fatti chiave:

  • pg_upgrade è distruttivo in place — in caso di successo il vecchio cluster sparisce, e un errore a metà strada può lasciare la directory dati convertita a metà. Il tuo backup fuori host è l’unico rollback.
  • Il file mantiene PGAUTO_ONESHOT=yes, il PGDATA=/var/lib/postgresql/data legacy, nessun healthcheck e restart: no, e fissa l’immagine per digest.

Passaggi

  1. Ferma lo stack e fai una copia grezza fuori host del volume A — è il tuo unico 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 .
    Copia pg16-volume-backup.tar.gz fuori dall’host.
  2. Esegui il file one-shot pronto contro il tuo volume in esecuzione (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
    Attendi che il container termini con exit 0.
  3. Verifica il layout — il volume ora deve essere PostgreSQL 18 sul percorso legacy:
    docker run --rm -v <your_postgres_volume>:/d:ro alpine:3.23 cat /d/PG_VERSION   # -> 18
  4. Avvia il tuo stack normale (postgres:18-alpine). Usa lo stesso volume, ora aggiornato — nessun passo di promozione necessario (l’upgrade era in place).
  5. Esegui la Verifica. Se qualcosa non va, ripristina A da pg16-volume-backup.tar.gz.

Piattaforme solo-UI (Coolify/Portainer): incolla il contenuto di infra/docker-compose.pgautoupgrade.unsafe.yml in un nuovo stack temporaneo, imposta le variabili sopra, distribuisci una volta, conferma l’uscita, rimuovi quello stack, poi punta il tuo stack normale allo stesso volume.


Promuovere B

Dopo la verifica, punta lo stack principale al volume migrato B. I file compose di base non vengono mai modificati (così le installazioni nuove restano intatte); la promozione è un override.

  • Docker / Compose puro: aggiungi l’override di promozione e imposta POSTGRES_VOLUME_NAME su B:
    POSTGRES_VOLUME_NAME=quotenode_postgres_upgraded \
      docker compose -f infra/docker-compose.coolify.yml \
                     -f infra/docker-compose.promote.yml up -d
    (usa docker-compose.prod.yml invece di coolify.yml per il file prod). Per tornare indietro, togli -f docker-compose.promote.yml e riavvia — sei di nuovo su A.
  • UI Coolify / Portainer: nella UI non c’è il concatenamento -f. Rimappa invece lo storage del servizio postgres sul volume B nelle impostazioni di storage della piattaforma, oppure aggiungi external: true + name: <B> per il volume postgres nell’editor compose dello stack.

Verifica

Dopo che lo stack principale gira su B:

  1. Apri /health — deve restituire UP.
  2. Accedi e conferma che i tuoi dati esistenti (preventivi, clienti, impostazioni) ci sono.
  3. Crea un preventivo di prova e genera un PDF.
  4. Apri un link pubblico per confermare il routing.

Solo dopo questo dovresti cancellare il volume A e gli artefatti di migrazione.


Ordine di rilascio (importante)

Se la tua piattaforma distribuisce automaticamente su un tag mobile, un’immagine che richiede PG18 potrebbe arrivare prima della migrazione, rompendo lo stack. Sempre:

  1. Metti in pausa l’auto-deploy / fissa il tag immagine attuale.
  2. Esegui la migrazione (percorso A o B) e promuovi B.
  3. Solo allora aggiorna l’applicazione alla build PostgreSQL 18 e riattiva l’auto-deploy.

Ripristino

Il volume di origine A non viene mai modificato, quindi il ripristino è semplice:

  • Migrazione interrotta prima della promozione (timeout, redeploy, stop): cancella il volume B e il volume degli artefatti, poi ricomincia. Non c’è uno stato parziale da riprendere.
  • Problema dopo la promozione: annulla la promozione (togli l’override / svuota POSTGRES_VOLUME_NAME o ripunta lo storage su A) e avvia lo stack — sei di nuovo su PostgreSQL 16 con tutti i dati.
  • Ultima risorsa: ripristina da backup-before-pg18.sql (vedi Aggiornare QuoteNode → Tornare indietro).

Non eseguire mai docker compose down -v durante questa procedura — cancella i volumi.

Last reviewed: Recently