Installation
Daten zu PostgreSQL 18 migrieren
Einmalige, datensichere Prozedur, um eine bestehende PostgreSQL-16-Installation ohne Datenverlust auf PostgreSQL 18 zu heben.
Daten zu PostgreSQL 18 migrieren
Wer das braucht: nur Installationen, die bereits auf PostgreSQL 16 laufen und auf einen QuoteNode-Build wechseln möchten, der PostgreSQL 18 voraussetzt. Neue Installationen brauchen nichts — sie starten direkt auf PostgreSQL 18.
Ein PostgreSQL-18-Server kann nicht auf einem PostgreSQL-16-Datenverzeichnis starten. Die Anwendung verweigert den Start auf einem inkompatiblen Verzeichnis und schreibt es niemals automatisch um, daher muss eine bestehende Installation ihre Daten einmal nach vorne bewegen. Das ist eine bewusste, einmalige Operator-Prozedur.
Die Migration ist von Grund auf datensicher: Ihr bestehendes Datenvolume (nennen wir es A) wird nie gelöscht, neu erstellt oder überschrieben. Die Daten werden in ein neues PostgreSQL-18-Volume (B) kopiert; A bleibt als sofortiges Rollback intakt, bis Sie bestätigen, dass B funktioniert.
Es gibt drei Wege (Weg C ist eine abgeratene letzte Möglichkeit). Wählen Sie danach, ob Sie eine Host-Shell haben:
| Weg | Wann verwenden | Mechanismus |
|---|---|---|
| A — Host-CLI | Sie können docker auf dem Host ausführen (Ubuntu VPS, LAN Linux, einfaches Compose) | scripts/migrate-postgres-major.sh |
| B — UI-only-Stack | Sie haben nur eine UI (Coolify, Portainer, QNAP, Synology) | infra/docker-compose.upgrade.yml |
| C — In-Place-Auto-Upgrade ⚠️ | Nur als letzte Möglichkeit; weder A noch B funktioniert und Sie akzeptieren das In-Place-Risiko | pgautoupgrade-Image (nicht empfohlen) |
Die Wege A und B sind datensicher: Sie kopieren in ein neues Volume und lassen das Original intakt. Weg C aktualisiert in place — es gibt kein unberührtes Rollback, nur Ihr Off-Host-Backup. Bevorzugen Sie A oder B.
Bevor Sie beginnen (alle Wege)
- Sichern Sie
.env. Sie enthält Geheimnisse, die nicht neu erzeugt werden können, besondersDB_ENCRYPTION_KEY. - Erstellen Sie ein Datenbank-Backup (zusätzlich zur automatischen Sicherheitskopie der Migration):
docker compose exec postgres pg_dump -U quotenode quotenode > backup-before-pg18.sql - Pausieren Sie Auto-Deploy, falls Ihre Plattform auf einem beweglichen Tag neu deployt (z. B.
Coolify-Auto-Deploy, Portainer
:latest). Siehe Release-Reihenfolge — ein PG18-Image darf nicht vor der Migration landen. - Stoppen Sie den Anwendungs-Stack (ohne Volumes zu löschen). Führen Sie niemals
docker compose down -vaus.
Weg A — Host-CLI (empfohlen, wenn Sie eine Shell haben)
Das ist der einfachste Weg für Ubuntu VPS, LAN Linux und jeden einfachen Docker-Compose-Host.
- Finden Sie den Namen des Datenvolumes:
docker volume ls | grep postgres - Stoppen Sie den Stack (Volumes behalten):
docker compose down - Führen Sie die Migration aus. Sie erfasst zwei Sicherheitsartefakte (einen logischen Dump und ein
rohes Tarball des Volumes) und verifiziert beide vor jeder Aktion, führt einen Fail-Closed-Preflight
auf dem laufenden Cluster aus und erstellt dann das Volume auf PostgreSQL 18 neu und stellt es wieder her:
Das Skript führt nichts Destruktives aus ohneDB_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=yesund bricht ab (Ihre Daten bleiben unberührt), wenn es unerwartete Rollen/Extensions oder zu wenig Speicherplatz findet. - Starten Sie den Stack normal. Er kommt auf PostgreSQL 18 hoch und wendet ausstehende Migrationen an:
docker compose up -d - Führen Sie die Überprüfung durch und löschen Sie dann die vom Skript ausgegebenen Sicherheitsartefakte, sobald Sie sicher sind.
Nicht-Standard-Locale: Wenn Ihre Quelldatenbank ein anderes Locale als das Image-Standard verwendet, bricht das Skript bei der Encoding/Collation-Prüfung fail-closed ab (eine stille Collation-Änderung kann Textindizes beschädigen). Führen Sie es erneut mit passendem
POSTGRES_INITDB_ARGSaus (z. B.--locale=...).
Hinweise für Coolify (verwaltete Hosts)
Weg A über SSH auf dem Coolify-Server ist die einfachste Route auf einem Coolify-Host. Einige Coolify-Besonderheiten, in der Praxis bestätigt:
- Ein „Failed”-Deployment vor der Migration ist zu erwarten. Wenn Coolify einen Build deployt, der
PostgreSQL 18 voraussetzt, auf ein bestehendes PostgreSQL-16-Volume, gerät der
postgres-Container in eine Crash-Schleife (FATAL: database files are incompatible with server) und Coolify markiert das Deployment als Failed. Ihre Daten sind nicht betroffen — PostgreSQL verweigert nur den Start auf einem älteren Datenverzeichnis. - Sie müssen den
postgres-Container entfernen, um zu migrieren. Weg A erstellt das Datenvolume neu, und Docker verweigert das Entfernen eines Volumes, solange irgendein Container — selbst ein gestoppter — es noch referenziert. Daher entfernt die Prozedur zuerst den Coolify-postgres-Container; Stoppen allein reicht nicht. - Nach der Datenmigration holen Sie den Stack mit „Redeploy” zurück — nicht „Restart”. Da der
postgres-Container entfernt wurde, muss Coolify ihn neu erstellen. Klicken Sie auf Redeploy der Ressource; Coolify startet dannpostgres:18-alpineauf dem migrierten Volume und holt die App zurück. - Coolify verwendet das migrierte Volume wieder. Das Neuerstellen des Volumes verwirft Coolifys Compose-Labels, aber Docker Compose (und damit Coolifys Redeploy) gleicht Volumes per Name ab und verwendet das bestehende — Ihre Daten bleiben. Behalten Sie die Sicherheitsartefakte, bis Sie die neu deployte App verifiziert haben.
- Finden Sie den echten Volume-Namen im Tab Storages der Ressource; er hat ein UUID-Präfix, z. B.
<uuid>_postgres-prod-data(beachten Sie die Bindestriche). Wenn Sie mehrere Stacks (z. B. eine Dev- und eine Main-Installation) auf einem Host betreiben, migrieren Sie jeden per eigenem Volume-Namen und fassen Sie den anderen nicht an.
Weg B — UI-only-Migrations-Stack (Coolify, Portainer, NAS)
Wenn Sie keine Host-Shell haben, verwenden Sie den dedizierten, temporären Migrations-Stack
infra/docker-compose.upgrade.yml. Er ist kein Teil eines normalen Deployments — bringen Sie ihn nur
für die Migration hoch und entfernen Sie ihn danach. Er nutzt Standard-PostgreSQL-Images, keinen
Docker-Socket und wird vollständig über Container-Entrypoints gesteuert.
Er führt diese Phasen automatisch aus und stoppt bei einem verifizierten Report — er promotet nie von selbst:
- cold-backup — ein unveränderliches Tar von Volume A (Rollback-Image), erstellt bevor irgendetwas A liest.
- dump — ein Fail-Closed-Preflight des laufenden Clusters + ein logischer Dump.
- restore — Wiederherstellung in ein frisches PostgreSQL-18-Volume B.
- verify — prüft Hauptversion, Schema, Extensions und Locale-Parität und schreibt
REPORT.txtin das Artefakt-Volume.
Schritte
-
Stoppen Sie den Anwendungs-Stack in der UI Ihrer Plattform (löschen Sie keine Volumes).
-
Finden Sie den Namen des bestehenden Datenvolumes (das ist A, verwendet als
SOURCE_VOLUME_NAME):- Coolify: das Volume hat das Ressourcen-UUID-Präfix, z. B.
<uuid>_postgres_prod_data. - Portainer / QNAP / Synology: das Präfix ist der Stack-Name, z. B.
<stack>_postgres_data. - Compose-Standard:
<project>_postgres_prod_data.
- Coolify: das Volume hat das Ressourcen-UUID-Präfix, z. B.
-
Deployen Sie den Migrations-Stack mit diesen Umgebungsvariablen:
Variable Wert SOURCE_VOLUME_NAMEdas bestehende Volume A aus Schritt 2 DB_NAMEDB_USERNAMEDB_PASSWORDIhre bestehenden Datenbank-Zugangsdaten TARGET_VOLUME_NAMEoptional; Standard quotenode_postgres_upgraded(das ist B)# Reines-Docker-Äquivalent dessen, was die UI ausführt: docker compose -f infra/docker-compose.upgrade.yml up -
Lesen Sie den Report. Wenn die Phase
verifyfertig ist, lesen SieREPORT.txtaus dem Artefakt-Volume (quotenode_pg_upgrade_artifacts). Er mussPASSsagen. Schlägt eine Phase fehl, stoppt der Stack sicher; A bleibt unberührt — beheben Sie die Ursache und versuchen Sie es erneut (löschen Sie zuerst B, siehe Wiederherstellung). -
Entfernen Sie den Migrations-Stack (Volumes behalten) und führen Sie dann B übernehmen aus.
Volume-Sichtbarkeit der Plattform (auf Ihrer Plattform prüfen): Weg B nimmt an, dass der Migrations-Stack das bestehende Volume A der Anwendung als
external-Volume per Name einhängen kann. Die meisten Docker-Hosts erlauben das. Wenn Ihre Plattform Volumes pro Stack/Ressource isoliert und der Migrations-Stack A nicht sieht, verwenden Sie die In-Place-Variante: ersetzen Sie temporär das Compose der Anwendung durchdocker-compose.upgrade.ymlinnerhalb derselben Ressource/desselben Projekts, damit es denselben Volume-Namespace erbt. Bestätigen Sie das auf Ihrer konkreten Plattform, bevor Sie sich in der Produktion darauf verlassen.
Weg C — In-Place-Auto-Upgrade-Image (sieht am einfachsten aus, aber riskant — nicht empfohlen) ⚠️
Nur als letzte Möglichkeit. Verwenden Sie das nur, wenn weder Weg A noch Weg B machbar ist und Sie die folgenden Risiken akzeptieren. Anders als A und B aktualisiert dieser Weg in place auf Ihrem bestehenden Volume A, daher gibt es kein unberührtes Rollback-Volume — Ihr Off-Host-Backup ist das einzige Sicherheitsnetz.
Die Idee sieht am einfachsten aus: lassen Sie ein selbst-aktualisierendes Image
(pgautoupgrade) einmal pg_upgrade auf Ihrem
bestehenden Volume ausführen und starten Sie dann das normale postgres:18-alpine darauf. Aber es ist
keine Ein-Zeilen-Tag-Änderung in Ihrem normalen Compose — der postgres-Service hat dort einen
command:-Override (der den Upgrade-Entrypoint komplett unterdrückt), restart: unless-stopped (was
den fertigen One-Shot in eine Schleife brächte) und einen Healthcheck (der den aktualisierenden Server als
unhealthy markiert). Alle drei brechen ein In-Place-Upgrade.
Das Repo liefert eine fertige, eigenständige One-Shot-Datei, die alle drei vermeidet:
infra/docker-compose.pgautoupgrade.unsafe.yml (das .unsafe ist Absicht). Kernfakten:
pg_upgradeist destruktiv in place — bei Erfolg ist der alte Cluster weg, und ein Fehler auf halbem Weg kann das Datenverzeichnis halb konvertiert hinterlassen. Ihr Off-Host-Backup ist das einzige Rollback.- Die Datei behält
PGAUTO_ONESHOT=yes, das Legacy-PGDATA=/var/lib/postgresql/data, keinen Healthcheck undrestart: nound pinnt das Image per Digest.
Schritte
- Stoppen Sie den Stack und erstellen Sie eine rohe Off-Host-Kopie von Volume A — das ist Ihr einziges Rollback:
Kopieren Siedocker 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.gzvom Host weg. - Führen Sie die fertige One-Shot-Datei gegen Ihr laufendes Volume aus (
SOURCE_VOLUME_NAME= Volume A):
Warten Sie, bis der Container mit exit 0 endet.SOURCE_VOLUME_NAME=<your_postgres_volume> \ DB_NAME=quotenode DB_USERNAME=quotenode DB_PASSWORD='...' \ docker compose -f infra/docker-compose.pgautoupgrade.unsafe.yml up - Verifizieren Sie das Layout — das Volume muss jetzt PostgreSQL 18 auf dem Legacy-Pfad sein:
docker run --rm -v <your_postgres_volume>:/d:ro alpine:3.23 cat /d/PG_VERSION # -> 18 - Starten Sie Ihren normalen Stack (
postgres:18-alpine). Er nutzt dasselbe, jetzt aktualisierte Volume — kein Promotion-Schritt nötig (das Upgrade war in place). - Führen Sie die Überprüfung durch. Wenn etwas nicht stimmt, stellen Sie A aus
pg16-volume-backup.tar.gzwieder her.
UI-only-Plattformen (Coolify/Portainer): fügen Sie den Inhalt von
infra/docker-compose.pgautoupgrade.unsafe.ymlin einen neuen temporären Stack ein, setzen Sie die obigen Variablen, deployen Sie einmal, bestätigen Sie den Exit, löschen Sie diesen Stack und richten Sie dann Ihren normalen Stack auf dasselbe Volume.
B übernehmen
Richten Sie nach der Verifizierung den Haupt-Stack auf das migrierte Volume B. Die Basis-Compose-Dateien werden nie bearbeitet (damit neue Installationen unberührt bleiben); die Übernahme ist ein Override.
- Reines Docker / Compose: fügen Sie den Promotion-Override hinzu und setzen Sie
POSTGRES_VOLUME_NAMEauf B:
(verwenden Sie für die Prod-DateiPOSTGRES_VOLUME_NAME=quotenode_postgres_upgraded \ docker compose -f infra/docker-compose.coolify.yml \ -f infra/docker-compose.promote.yml up -ddocker-compose.prod.ymlstattcoolify.yml). Zum Zurückrollen entfernen Sie-f docker-compose.promote.ymlund starten erneut — Sie sind zurück auf A. - Coolify-/Portainer-UI: in der UI gibt es keine
-f-Verkettung. Mappen Sie stattdessen den Storage despostgres-Service in den Storage-Einstellungen der Plattform auf Volume B um, oder fügen Sieexternal: true+name: <B>für das Postgres-Volume im Compose-Editor des Stacks hinzu.
Überprüfen
Nachdem der Haupt-Stack auf B läuft:
- Öffnen Sie
/health— sollteUPzurückgeben. - Melden Sie sich an und bestätigen Sie, dass Ihre bestehenden Daten (Angebote, Kunden, Einstellungen) vorhanden sind.
- Erstellen Sie ein Testangebot und generieren Sie ein PDF.
- Öffnen Sie einen öffentlichen Link, um das Routing zu bestätigen.
Erst danach sollten Sie Volume A und die Migrationsartefakte löschen.
Release-Reihenfolge (wichtig)
Wenn Ihre Plattform auf einem beweglichen Tag automatisch deployt, könnte ein PG18-Image vor Ihrer Migration landen und den Stack zerstören. Immer:
- Pausieren Sie Auto-Deploy / pinnen Sie den aktuellen Image-Tag.
- Führen Sie die Migration aus (Weg A oder B) und übernehmen Sie B.
- Erst dann aktualisieren Sie die Anwendung auf den PostgreSQL-18-Build und aktivieren Auto-Deploy wieder.
Wiederherstellung
Das Quellvolume A wird nie verändert, daher ist die Wiederherstellung einfach:
- Migration vor der Übernahme unterbrochen (Timeout, Redeploy, Stopp): löschen Sie Volume B und das Artefakt-Volume und beginnen Sie von vorn. Es gibt keinen Teilzustand zum Fortsetzen.
- Problem nach der Übernahme: machen Sie die Übernahme rückgängig (Override entfernen /
POSTGRES_VOLUME_NAMEleeren oder den Storage zurück auf A richten) und starten Sie den Stack — Sie sind zurück auf PostgreSQL 16 mit allen Daten. - Letzte Möglichkeit: stellen Sie aus
backup-before-pg18.sqlwieder her (siehe QuoteNode aktualisieren → Zurückrollen).
Führen Sie während dieser Prozedur niemals docker compose down -v aus — es löscht Volumes.