-
Notifications
You must be signed in to change notification settings - Fork 0
De Install Backup Restore
Eine plMail-Installation besteht aus drei Dingen, und eine Sicherung, die nur zwei davon enthält,
stellt nichts Brauchbares wieder her. Diese Seite sagt, welche drei das sind, was app:backup mit
ihnen macht und wie du eine Installation auf einer anderen Maschine hochbringst.
| Was | Wo es liegt | Warum der Verlust wehtut |
|---|---|---|
| Die Datenbank | im Volume database_data
|
Jede Nachricht, jede Konversation, jedes Label, jeder Filter, jeder Kalender, jeder Benutzer und jedes App-Passwort |
| Der Verschlüsselungsschlüssel |
APP_ENCRYPTION_KEY in var/secrets/generated.env, auf dem Volume app_secrets
|
Ohne ihn ist jedes Postfachpasswort und jedes OAuth-Token in der Datenbank dauerhaft unlesbar |
| Die Blobs |
attachments/, raw/ und uploads/ unterhalb von APP_STORAGE_DIR
|
Anhangpfade werden relativ zum Projektstammverzeichnis in der Datenbank gespeichert; ohne die Dateien endet nach einer Wiederherstellung jeder Anhang mit 404 |
Die Datei mit den Geheimnissen enthält mehr als den Schlüssel — APP_SECRET, POSTGRES_PASSWORD,
MERCURE_JWT_SECRET, das VAPID-Schlüsselpaar und die bei der Einrichtung gespeicherte
APP_PUBLIC_URL —, und das JWT-Schlüsselpaar liegt daneben in var/secrets/jwt/. All das lässt
sich neu erzeugen; der Verschlüsselungsschlüssel nicht.
Die Konfiguration — die Umgebungswerte, jene Dateien im Secrets-Volume und die Zugangsdaten, die ein Administrator in ein Formular getippt hat — lässt sich auch für sich allein mitnehmen, ohne jede E-Mail, als eine einzige verschlüsselte Datei: siehe Konfigurationssicherung. Das ist die Datei, die du willst, wenn du eine Installation neu aufbaust statt eine wiederherstellst.
Zwei Volumes brauchen überhaupt keine Sicherung: caddy_data/caddy_config und
mercure_data/mercure_config enthalten TLS-Material und Hub-Zustand, die sich von selbst wieder
herstellen.
Der typische Fehlerfall ist, die Datenbank zu sichern und sonst nichts. Das stellt jedes Postfach mit Zugangsdaten wieder her, die niemand entschlüsseln kann, und jeden Anhanglink zeigend auf eine Datei, die nicht da ist. Keines von beidem meldet sich, bis jemand versucht, etwas abzugleichen oder zu öffnen.
Ein Befehl für alle drei:
docker compose exec php php bin/console app:backup /path/inside/the/containerOhne Argument schreibt er nach var/backups/<Y-m-d_His>/ im Projektverzeichnis. Das Ziel wird mit
0700 angelegt und enthält:
database.sql pg_dump of everything, --no-owner --no-privileges, mode 0600
attachments/ copied with cp -a
raw/
uploads/ includes avatars, under uploads/avatars/
secrets.env a copy of var/secrets/generated.env, mode 0600
Zwei Optionen: --skip-secrets, wenn der Schlüssel anderswo bereits gesichert ist, und
--skip-storage für eine Momentaufnahme nur der Datenbank.
pg_dump läuft gegen die geparste DATABASE_URL — eine Verbindung, die die Anwendung nutzen kann,
kann also auch der Dump nutzen —, und das Passwort wird über PGPASSWORD in der Umgebung übergeben
statt auf der Kommandozeile, wo ps es jedem Benutzer des Hosts zeigen würde. Das Image liefert
postgresql-client-18 aus PGDG mit Absicht mit: pg_dump weigert sich schlicht, einen Server zu
sichern, der neuer ist als es selbst, und das Debian-Paket hinkt hinterher.
Der Befehl schließt mit einer von zwei Aussagen, und beide sind lesenswert:
-
secrets.enventhältAPP_ENCRYPTION_KEY— leg es an einen Ort, an dem der Datenbank-Dump nicht liegt. Postfachpasswörter zu verschlüsseln ist sinnlos, wenn die Sicherung den Schlüssel daneben heftet; zusammen aufbewahrt ist das Paar für einen Dieb exakt so viel wert wie eine unverschlüsselte Sicherung. -
APP_ENCRYPTION_KEYist nicht in dieser Sicherung — diese Installation bekommt ihn aus der Umgebung, es gab also nichts zu kopieren. Die Sicherung sieht vollständig aus und ist es nicht. Sichere den Schlüssel dort, wo du ihn konfigurierst.
cp -a statt einer flachen Kopie ist Absicht: Die Speicherpfade in der Datenbank zeigen in die
Verzeichnisaufteilung hinein, eine flache Kopie stellt also Dateien wieder her, die niemand findet.
Der typische Fehlerfall ist, dass der php-Container die Blobs nicht sehen kann. app:backup
kopiert APP_STORAGE_DIR so, wie dieser Container es sieht, und in der Standard-compose.yaml
liegen die Blob-Verzeichnisse auf keinem gemeinsamen Volume — die Kopie des Web-Containers ist also
nicht die, in die der Ingest-Worker geschrieben hat. Bring die Mounts in Ordnung, bevor du der
Sicherung traust; siehe
den Speicherabschnitt der Docker-Seite.
app:backup fehlt bewusst in MaintenanceSchedule. Eine Sicherung, die sich selbst auf dieselbe
Platte legt wie das, was sie sichert, ist eine trügerische Sicherheit, und wohin sie stattdessen
gehört, kannst nur du entscheiden. Steuere sie über cron auf dem Host oder über das, was dein NAS
ohnehin schon verwendet:
docker compose exec -T php php bin/console app:backup /app/var/backups/nightlyLeg database.sql und secrets.env anschließend an unterschiedliche Orte. Genau darum geht es
bei Verschlüsselung im Ruhezustand.
Der typische Fehlerfall ist eine Sicherung, die noch nie jemand zurückgespielt hat. Die erste Wiederherstellung ist nicht der Moment, in dem du entdecken willst, dass die Blob-Verzeichnisse leer waren.
Die Reihenfolge zählt, denn die Geheimnisse müssen an Ort und Stelle sein, bevor irgendetwas in die Datenbank schreibt.
1. Leg die Compose-Datei auf den neuen Host und starte den Stack nicht. Falls er schon gestartet
wurde, fahr ihn herunter und entferne seine Volumes — eine Installation, die sich ihr eigenes
APP_ENCRYPTION_KEY erzeugt hat, verweigert gegen eine wiederhergestellte Datenbank ohnehin den
Start.
2. Leg secrets.env als generated.env zurück. Ermittle den Volume-Namen
(docker volume ls; er ist dein Projektname plus _app_secrets) und schreib die Datei hinein:
docker run --rm -v pl_mail_app_secrets:/secrets -v "$PWD":/backup:ro alpine \
sh -c 'cp /backup/secrets.env /secrets/generated.env && chmod 600 /secrets/generated.env'Die nackte Datei postgres_password, die das Postgres-Image liest, muss nicht wiederhergestellt
werden: Der Generator schreibt sie bei jedem Lauf aus der Zeile POSTGRES_PASSWORD= in
generated.env neu. Auch das JWT-Schlüsselpaar muss nicht wiederhergestellt werden —
app:secrets:init erzeugt es neu, wenn es fehlt, um den Preis, dass jedes bereits ausgestellte
JMAP-JWT ungültig wird. App-Passwörter sind Datenbankzeilen und davon unberührt.
3. Bring nur die Datenbank hoch und warte auf sie.
docker compose up -d databasesecrets-init läuft zuerst, findet jeden Wert bereits in der wiederhergestellten Datei vor und
erzeugt nichts.
4. Spiel den Dump ein.
docker compose exec -T database psql -U app -d app < database.sql5. Leg die Blobs zurück in das, was sie auf diesem Host aufnimmt — die benannten Volumes, falls
du sie ergänzt hast, oder das bind-gemountete Verzeichnis, falls du dem Muster aus
truenas.compose.yaml gefolgt bist. Worauf es ankommt: Die Pfade unterhalb von APP_STORAGE_DIR
müssen dieselben sein wie vorher, denn die Datenbank speichert sie relativ zum
Projektstammverzeichnis.
6. Starte alles.
docker compose up -dMigrationen laufen beim Start, eine Wiederherstellung auf ein neueres Image bringt das Schema also beim Starten mit nach vorn. Eine Wiederherstellung auf ein älteres Image geht nicht rückwärts — siehe Aktualisieren.
7. Prüfe drei Dinge. /healthz sollte mit 200 und database: true antworten; die Kopfzeile im
Administrationsbereich sollte den erwarteten Build zeigen; und ein Mail-Konto sollte sich abgleichen.
Letzteres ist die eigentliche Probe, denn es ist das Erste, was gespeicherte Zugangsdaten
entschlüsselt.
Der typische Fehlerfall ist ein Container, der mit "APP_ENCRYPTION_KEY cannot decrypt the credentials already stored in this database" den Start verweigert. Diese Meldung bedeutet, dass die Wiederherstellung funktioniert hat und der Schlüssel nicht mitgekommen ist. Nichts wurde verändert — die Prüfung verweigert, statt Daten zu überschreiben, die der richtige Schlüssel noch lesen könnte. Den ursprünglichen Schlüssel zurückzulegen ist der einzige Weg, diese Zugangsdaten wiederzubekommen.
Eine auf einem neuen Host wiederhergestellte Installation wird meist unter einer neuen Adresse
erreicht. APP_PUBLIC_URL steht in der wiederhergestellten generated.env und enthält daher noch
die alte. Setz sie in der Umgebung oder bearbeite diese Datei, starte den Stack neu — und prüfe die
bei Google und Microsoft registrierten
Redirect-URIs erneut, die exakt abgeglichen werden.
Der typische Fehlerfall ist Push, das nie wieder anläuft. Die von der alten Installation
registrierten Kanäle zeigen auf die alte Adresse; app:calendar:push registriert stündlich neu,
sobald die Adresse stimmt, und app:push:renew --repair läuft nächtlich für Mail.
Das Volume database_data zu kopieren ist keine Sicherung. Ein laufender Postgres-Cluster, Datei
für Datei kopiert, ist nicht konsistent. pg_dump — und genau das führt app:backup aus — ist es.
database.sql und secrets.env zusammen zu lagern hebt die Verschlüsselung im Ruhezustand auf.
Der Befehl sagt das laut, einmal, am Ende eines Laufs, den niemand zweimal liest.
Eine Installation, die APP_ENCRYPTION_KEY aus der Umgebung bekommt, erhält eine Sicherung ohne
Schlüssel darin. Das ist korrektes Verhalten und der gefährlichste Fall auf dieser ganzen Seite —
deshalb prüft der Befehl, ob der Schlüssel in der kopierten Datei vorhanden ist, statt es
anzunehmen.
POSTGRES_PASSWORD lässt sich nicht dadurch wechseln, dass man eine andere Geheimnisdatei
wiederherstellt. Postgres wurde mit dem alten initialisiert und behält seine eigene Kopie, die
Anwendung ist also aus einem Cluster ausgesperrt, den sie ansonsten sieht. Stell entweder das
passende Passwort wieder her oder fang mit einem leeren Datenbank-Volume an.
Sicherungen verlieren mit dem Image an Brauchbarkeit. Ein Dump von einer Installation mit einem deutlich älteren Tag lässt sich einwandfrei einspielen und migriert beim ersten Start nach vorn; ein Dump von einer neueren Installation migriert nicht rückwärts. Notiere den Versions-Chip aus der Kopfzeile des Administrationsbereichs zusammen mit der Sicherung.
This page is generated from docs/de/install/backup-restore.md. Edit it there — changes made here are overwritten on the next push to main.
Using plMail
- Accounts and aliases
- Account health
- Filters
- Calendar
- Invitations and events from mail
- Reminders
- Connected calendars
- Sharing and booking
- Files and integrations
- Security
- Other clients
- Appearance
- Administration
Installing and running
- Docker Compose
- Platform notes
- Behind a reverse proxy
- Configuration reference
- Backup and restore
- Configuration backup
- Upgrading
- Demo mode
- Troubleshooting
Providers
How it works