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:
| Percorso | Quando usarlo | Meccanismo |
|---|---|---|
| A — CLI dell’host | Puoi eseguire docker sull’host (Ubuntu VPS, LAN Linux, Compose semplice) | scripts/migrate-postgres-major.sh |
| B — stack solo-UI | Hai 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 place | immagine 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)
- Salva
.env. Contiene segreti non rigenerabili, in particolareDB_ENCRYPTION_KEY. - 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 - Metti in pausa l’auto-deploy se la tua piattaforma ridistribuisce su un tag mobile (es. auto-deploy
di Coolify,
:latestdi Portainer). Vedi Ordine di rilascio — un’immagine PG18 non deve arrivare prima dell’esecuzione della migrazione. - 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.
- Trova il nome del volume dati:
docker volume ls | grep postgres - Ferma lo stack (mantieni i volumi):
docker compose down - 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:
Lo script non fa nulla di distruttivo senzaDB_NAME=quotenode DB_USERNAME=quotenode DB_PASSWORD='...' \ POSTGRES_VOLUME=<your_postgres_volume> \ DB_MAJOR_MIGRATE_CONFIRM=yes \ ./scripts/migrate-postgres-major.shDB_MAJOR_MIGRATE_CONFIRM=yese si interrompe (lasciando i tuoi dati intatti) se trova ruoli/extension inattesi o spazio su disco insufficiente. - Avvia lo stack normalmente. Salirà su PostgreSQL 18 e applicherà le migrazioni in sospeso:
docker compose up -d - 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_ARGScorrispondente (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
postgresva 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
postgresper 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 containerpostgresdi 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 allorapostgres:18-alpinesul 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:
- cold-backup — un tar immutabile del volume A (immagine di rollback), preso prima che qualcosa legga A.
- dump — un preflight fail-closed del cluster in esecuzione + un dump logico.
- restore — ripristino in un nuovo volume PostgreSQL 18 B.
- verify — controlla versione maggiore, schema, extension e parità di locale, e scrive
REPORT.txtnel volume degli artefatti.
Passaggi
-
Ferma lo stack dell’applicazione nella UI della tua piattaforma (non cancellare i volumi).
-
Trova il nome del volume dati esistente (è A, 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.
- Coolify: il volume ha il prefisso dell’UUID della risorsa, es.
-
Distribuisci lo stack di migrazione con queste variabili d’ambiente:
Variabile Valore SOURCE_VOLUME_NAMEil volume esistente A del passaggio 2 DB_NAMEDB_USERNAMEDB_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 -
Leggi il report. Quando la fase
verifyfinisce, leggiREPORT.txtdal volume degli artefatti (quotenode_pg_upgrade_artifacts). Deve direPASS. Se una fase fallisce, lo stack si ferma in modo sicuro; A è intatto — correggi la causa e riprova (cancella prima B, vedi Ripristino). -
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
externalper 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 condocker-compose.upgrade.ymlnella 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, ilPGDATA=/var/lib/postgresql/datalegacy, nessun healthcheck erestart: no, e fissa l’immagine per digest.
Passaggi
- Ferma lo stack e fai una copia grezza fuori host del volume A — è il tuo unico rollback:
Copiadocker run --rm -v <your_postgres_volume>:/d:ro -v "$PWD:/out" alpine:3.23 \ tar czf /out/pg16-volume-backup.tar.gz -C /d .pg16-volume-backup.tar.gzfuori dall’host. - Esegui il file one-shot pronto contro il tuo volume in esecuzione (
SOURCE_VOLUME_NAME= volume A):
Attendi che il container termini con exit 0.SOURCE_VOLUME_NAME=<your_postgres_volume> \ DB_NAME=quotenode DB_USERNAME=quotenode DB_PASSWORD='...' \ docker compose -f infra/docker-compose.pgautoupgrade.unsafe.yml up - 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 - Avvia il tuo stack normale (
postgres:18-alpine). Usa lo stesso volume, ora aggiornato — nessun passo di promozione necessario (l’upgrade era in place). - 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.ymlin 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_NAMEsu B:
(usaPOSTGRES_VOLUME_NAME=quotenode_postgres_upgraded \ docker compose -f infra/docker-compose.coolify.yml \ -f infra/docker-compose.promote.yml up -ddocker-compose.prod.ymlinvece dicoolify.ymlper il file prod). Per tornare indietro, togli-f docker-compose.promote.ymle riavvia — sei di nuovo su A. - UI Coolify / Portainer: nella UI non c’è il concatenamento
-f. Rimappa invece lo storage del serviziopostgressul volume B nelle impostazioni di storage della piattaforma, oppure aggiungiexternal: true+name: <B>per il volume postgres nell’editor compose dello stack.
Verifica
Dopo che lo stack principale gira su B:
- Apri
/health— deve restituireUP. - Accedi e conferma che i tuoi dati esistenti (preventivi, clienti, impostazioni) ci sono.
- Crea un preventivo di prova e genera un PDF.
- 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:
- Metti in pausa l’auto-deploy / fissa il tag immagine attuale.
- Esegui la migrazione (percorso A o B) e promuovi B.
- 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_NAMEo 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.