Datenbanken in Containern sichern
Wenn du eine Datenbank als Container betreibst, wird deren Volume vom regulären mittwald Projekt-Backup mitgesichert. Das schützt dich davor, das Volume zu verlieren — es garantiert aber nicht, dass die Datenbankdateien darin wiederherstellbar sind.
Ein Projekt-Backup kopiert das Volume im laufenden Betrieb. Die Datenbank hält Daten im Arbeitsspeicher, in Write-Ahead-Logs und in nur teilweise geschriebenen Pages — eine Kopie zu einem beliebigen Zeitpunkt kann also einen zerrissenen Zustand erwischen. Die Wiederherstellung kann funktionieren, eine Crash Recovery erfordern oder fehlschlagen. Du merkst es erst im Ernstfall.
Ein SQL-Dump vermeidet das: Die Datenbank selbst schreibt einen konsistenten Abzug ihres Inhalts. Diese Anleitung zeigt, wie du einen solchen Dump per Cronjob einrichtest, sodass beim Projekt-Backup immer eine wiederherstellbare Kopie im Volume liegt.
Funktionsweise
Das Setup besteht aus drei Bausteinen, die aufeinander aufbauen:
- Ein Cronjob führt einen Dump-Befehl in deinem Datenbank-Container aus.
- Der Dump wird als komprimierte
.sql.gz-Datei in ein Volume geschrieben. Alte Dumps werden nach einer Aufbewahrungsfrist gelöscht. - Das mittwald Projekt-Backup erfasst dieses Volume bei seinem regulären Lauf, sodass der Dump zusammen mit allem anderen im Projekt gesichert wird.
Du erhältst damit zwei Schichten: Das Projekt-Backup stellt das Volume wieder her, der Dump darin stellt die Datenbank wieder her.
Voraussetzungen
Für diese Anleitung benötigst du:
- Ein mittwald Projekt mit einem Hosting-Produkt, das Container-Workloads unterstützt
- Einen laufenden PostgreSQL-, MariaDB- oder MySQL-Container
- Für den CLI- und API-Weg: die mittwald CLI (
mw) installiert und eingeloggt (siehe die CLI-Dokumentation) sowie die Stack-ID und den Service-Namen des Containers (mw stack listundmw container listzeigen beides an). Alle Schritte bis einschließlich der Überprüfung funktionieren auch vollständig im mStudio UI.
Schritt 1: Ablageort des Dumps
Der Dump landet in einem Unterverzeichnis des Volumes, das bereits deine Datenbankdaten enthält. Dieses Volume wird vom Projekt-Backup erfasst, der Dump wird also mitgesichert — ein zusätzliches Volume und eine Änderung am Stack sind nicht nötig.
Genau so machen es auch die Container-Templates:
- PostgreSQL:
/var/lib/postgresql/backups - MariaDB und MySQL:
/var/lib/mysql/backups
Schritt 2: Dump-Befehl auswählen
Die folgenden Befehle schreiben einen mit Zeitstempel versehenen, gzip-komprimierten Dump und löschen Dumps, die älter als sieben Tage sind. Schlägt der Dump fehl, brechen sie ab, ohne eine abgeschnittene Datei zu hinterlassen — ein fehlgeschlagener Lauf überschreibt also nie dein letztes funktionierendes Backup mit einem kaputten.
Passe den Pfad hinter d= an, falls dein Volume an einer anderen Stelle gemountet
ist (siehe Schritt 1).
- PostgreSQL
- MariaDB
- MySQL
sh -lc 'set -e; d=/var/lib/postgresql/backups; mkdir -p "$d";
f=$d/backup_$(date +%F_%H%M%S).sql;
{ pg_dumpall -h localhost -U "$POSTGRES_USER" -f "$f"; } || { rm -f "$f"; exit 1; };
gzip "$f";
find "$d" -name "backup_*.sql.gz" -type f -mtime +7 -delete'
pg_dumpall sichert den gesamten Cluster inklusive aller Datenbanken und Rollen.
Die Anmeldung über localhost funktioniert ohne Passwort, weil die offiziellen
Images in ihrer generierten pg_hba.conf lokale TCP-Verbindungen mit trust
zulassen.
Um stattdessen nur eine einzelne Datenbank zu sichern, ersetze pg_dumpall durch
pg_dump -h localhost -U "$POSTGRES_USER" -d "$POSTGRES_DB".
sh -lc 'set -e; d=/var/lib/mysql/backups; mkdir -p "$d";
f=$d/backup_$(date +%F_%H%M%S).sql;
{ MYSQL_PWD="$BACKUP_PASSWORD" mariadb-dump -u root --all-databases --routines --events --single-transaction --result-file="$f"; } || { rm -f "$f"; exit 1; };
gzip "$f";
find "$d" -name "backup_*.sql.gz" -type f -mtime +7 -delete'
--single-transaction erstellt den Dump innerhalb einer Transaktion. Das ergibt
einen konsistenten Abzug der InnoDB-Tabellen, ohne sie zu sperren — deine Anwendung
kann währenddessen weiter schreiben. Ohne diese Option sperrt mariadb-dump
sämtliche Tabellen für die Dauer des Dumps.
sh -lc 'set -e; d=/var/lib/mysql/backups; mkdir -p "$d";
f=$d/backup_$(date +%F_%H%M%S).sql;
{ MYSQL_PWD="$MYSQL_ROOT_PASSWORD" mysqldump -u root --all-databases --routines --events --single-transaction --result-file="$f"; } || { rm -f "$f"; exit 1; };
gzip "$f";
find "$d" -name "backup_*.sql.gz" -type f -mtime +7 -delete'
--single-transaction erstellt den Dump innerhalb einer Transaktion. Das ergibt
einen konsistenten Abzug der InnoDB-Tabellen, ohne sie zu sperren — deine Anwendung
kann währenddessen weiter schreiben.
Schritt 3: Cronjob anlegen
- mStudio UI
- CLI
- API
- Navigiere zu dem Projekt, in dem dein Datenbank-Container läuft.
- Wähle in der Seitenleiste unter "Komponenten" den Menüpunkt "Cronjobs".
- Klicke auf "Anlegen".
- Vergib einen Namen und stelle das Ziel von "App" auf "Container" um.
- Wähle unter "Verknüpfter Container" deinen Datenbank-Container aus.
- Trage den Dump-Befehl aus Schritt 2 unter "Auszuführender Befehl" ein.
- Belasse das Intervall auf "Cron-Syntax" und trage einen Zeitplan ein, zum
Beispiel
0 3 * * *für einen täglichen Lauf um 03:00 Uhr. Setze die Zeitzone aufEurope/Berlin. - Klicke auf "Anlegen".
Der Dialog hat kein Feld für Fehlerbenachrichtigungen. Richte sie im Anschluss ein: Öffne den Cronjob und klicke im Abschnitt "Fehlerbehandlung" auf "Bearbeiten", um Timeout und E-Mail-Adresse zu hinterlegen. Standardmäßig benachrichtigt mittwald dich ab dem ersten fehlgeschlagenen Lauf.
Verwende den Befehl mw cronjob create mit dem Flag --container-id, das einen
Container-Service statt einer App-Installation als Ziel setzt:
user@local $ mw cronjob create \
--container-id postgres \
--description "PostgreSQL dump" \
--interval "0 3 * * *" \
--timezone "Europe/Berlin" \
--timeout 1h \
--email ops@example.com \
--command 'sh -lc '\''set -e; d=/var/lib/postgresql/backups; mkdir -p "$d"; f=$d/backup_$(date +%F_%H%M%S).sql; { pg_dumpall -h localhost -U "$POSTGRES_USER" -f "$f"; } || { rm -f "$f"; exit 1; }; gzip "$f"; find "$d" -name "backup_*.sql.gz" -type f -mtime +7 -delete'\'''
--container-id akzeptiert die Container-ID, deren Short-ID oder den
Service-Namen. --interpreter und --url sind für Container-Cronjobs nicht
zulässig; der Befehl läuft direkt im Container.
Verwende die Operation POST/
mit einem ServiceTarget, das einen Container-Service über Stack-ID und
Service-Namen adressiert:
POST /v2/projects/<project-id>/cronjobs HTTP/1.1
Host: api.mittwald.de
Content-Type: application/json
{
"description": "PostgreSQL dump",
"interval": "0 3 * * *",
"timeZone": "Europe/Berlin",
"active": true,
"timeout": 3600,
"concurrencyPolicy": "forbid",
"email": "ops@example.com",
"failedExecutionAlertThreshold": 1,
"target": {
"stackId": "11111111-2222-3333-4444-555555555555",
"serviceIdentifier": "postgres",
"command": "sh -lc 'set -e; d=/var/lib/postgresql/backups; mkdir -p \"$d\"; f=$d/backup_$(date +%F_%H%M%S).sql; { pg_dumpall -h localhost -U \"$POSTGRES_USER\" -f \"$f\"; } || { rm -f \"$f\"; exit 1; }; gzip \"$f\"; find \"$d\" -name \"backup_*.sql.gz\" -type f -mtime +7 -delete'"
}
}
/v2/projects/{projectId}/cronjobs/ Zwei Einstellungen verdienen besondere Aufmerksamkeit:
concurrencyPolicy: "forbid"verhindert, dass ein zweiter Dump startet, während der vorherige noch läuft. Überlappende Dumps konkurrieren um dieselbe Datenbank und können das Volume volllaufen lassen.timeoutwird in Sekunden angegeben und beträgt standardmäßig eine Stunde. Große Datenbanken brauchen einen höheren Wert; nach Ablauf des Timeouts wird der Dump abgebrochen, und der Befehl entfernt die unvollständige Datei.
mw cronjob create deckt das Timeout mit dem Flag --timeout ab, kennt aber keine
Entsprechung für concurrencyPolicy — die setzt du über die API.
Lege den Dump zeitlich so, dass er vor deinem Projekt-Backup abgeschlossen ist — und außerhalb der Hauptlastzeiten.
Schritt 4: Das Backup überprüfen
Ein Backup, das du nie getestet hast, ist eine Annahme und kein Backup. Löse den Cronjob einmal manuell aus und prüfe das Ergebnis.
- mStudio UI
- CLI
- Öffne den Cronjob und klicke im Abschnitt "Intervall" auf "Jetzt ausführen".
- Wechsle auf den Tab "Historie". Dort erscheint der Lauf mit Datum und Laufzeit.
- Ein fehlgeschlagener Lauf ist mit "Ausführung fehlgeschlagen" markiert. Öffne bei diesem Eintrag das Menü "..." und wähle "Log anzeigen" — dort stehen Exit-Code und Fehlerausgabe.
Auch die Cronjob-Übersicht kennzeichnet fehlschlagende Cronjobs mit "Ausführung fehlgeschlagen". Ein Blick in diese Liste zeigt dir also, ob deine Backups noch laufen. Mach dir das zur Gewohnheit — ein Backup-Cronjob kann wochenlang fehlschlagen, ohne dass sonst etwas auffällt.
Eine Laufzeit von 0 Sekunden zusammen mit einem Fehlschlag bedeutet meist, dass der Dump gar nicht erst eine Verbindung zur Datenbank bekommen hat.
Prüfe, ob tatsächlich ein Dump geschrieben wurde. Die Beispiele nutzen den
PostgreSQL-Pfad; bei MariaDB und MySQL lautet er /var/lib/mysql/backups (siehe
Schritt 1).
user@local $ mw container exec <container-id> "ls -la /var/lib/postgresql/backups"
Achte dabei auf drei Dinge:
-
Die Datei existiert und trägt den aktuellen Zeitstempel.
-
Die Größe ist plausibel. Ein Dump von wenigen hundert Byte bedeutet meist, dass der Befehl sich zwar verbinden konnte, aber keine Daten gefunden hat.
-
Der Inhalt ist lesbar. Entpacke die Datei und sieh dir den Anfang an:
user@local $ mw container exec <container-id> "gunzip -c /var/lib/postgresql/backups/backup_*.sql.gz | head -20"
Um eine Kopie außerhalb der Plattform vorzuhalten, lädst du den Dump mit
mw container cp herunter:
user@local $ mw container cp <container-id>:/var/lib/postgresql/backups/backup_2026-09-07_030000.sql.gz ./
Wiederhole diese Prüfung nach jeder Änderung an der Datenbankversion, am Volume-Layout oder am Dump-Befehl.
Einen Dump wiederherstellen
Stelle zuerst das Volume aus dem Projekt-Backup wieder her, falls der Container selbst nicht mehr existiert, und spiele anschließend den Dump in die laufende Datenbank ein.
Ein Restore führt einen Befehl im Container aus — das bietet das mStudio UI von sich aus nicht. Dafür gibt es drei Wege:
- Web-SSH — installiere die kostenlose Extension 1-Click Web-SSH für Apps & Container aus dem Marktplatz. Sie gibt dir ein Terminal für Apps und Container direkt im mStudio, ohne lokale CLI und ohne SSH-Schlüssel. Im selben Terminal kannst du auch die Dump-Dateien im Volume ansehen.
- CLI —
mw container ssh <container-id>öffnet eine Shell im Container. - Beliebiger SSH-Client —
mw container ssh <container-id> --infogibt Hostname und Benutzernamen aus, ohne sich zu verbinden. Damit kannst du OpenSSH oder jeden anderen Client nutzen:ssh <benutzername>@<hostname>.
Alle drei Wege bringen dich in den Container. Sieh dir dort zuerst an, welche Dumps
vorliegen — bei MariaDB und MySQL liegt das Verzeichnis unter
/var/lib/mysql/backups (siehe Schritt 1):
user@container $ ls -la /var/lib/postgresql/backups
Anschließend spielst du den gewünschten Dump ein:
- PostgreSQL
- MariaDB
- MySQL
user@container $ gunzip -c /var/lib/postgresql/backups/backup_2026-09-07_030000.sql.gz | psql -h localhost -U "$POSTGRES_USER" -d postgres
Ein pg_dumpall-Dump legt Rollen und Datenbanken neu an. Spielst du ihn in einen
Cluster ein, in dem diese bereits existieren, meldet psql dafür
already exists-Fehler und fährt fort — die Tabellendaten werden trotzdem
wiederhergestellt. Für einen sauberen Restore löschst du die Zieldatenbank vorher
und legst sie neu an, oder du spielst den Dump in einen leeren Container ein.
user@container $ gunzip -c /var/lib/mysql/backups/backup_2026-09-07_030000.sql.gz | MYSQL_PWD="$BACKUP_PASSWORD" mariadb -u root
Der Dump enthält CREATE DATABASE ... IF NOT EXISTS- und
DROP TABLE IF EXISTS-Anweisungen und ersetzt damit die enthaltenen Tabellen.
Tabellen, die erst nach dem Dump angelegt wurden, werden nicht entfernt.
user@container $ gunzip -c /var/lib/mysql/backups/backup_2026-09-07_030000.sql.gz | MYSQL_PWD="$MYSQL_ROOT_PASSWORD" mysql -u root
Der Dump enthält CREATE DATABASE ... IF NOT EXISTS- und
DROP TABLE IF EXISTS-Anweisungen und ersetzt damit die enthaltenen Tabellen.
Tabellen, die erst nach dem Dump angelegt wurden, werden nicht entfernt.
Da diese Dumps die Rollendefinitionen (PostgreSQL) beziehungsweise die
Systemdatenbank mysql (MariaDB und MySQL) enthalten, werden Datenbankbenutzer und
deren Passwörter zusammen mit den Daten wiederhergestellt.
Weiterführende Ressourcen
- Container — Volumes, Stacks und Projekt-Backups
- mittwald Container-Templates