Skip to main content

Backing up databases running in containers

When you run a database as a container, its volume is included in the regular mittwald project backup. That protects you against losing the volume — but it does not guarantee that the database files inside it are restorable.

A project backup copies the volume while the database is running. The database keeps data in memory, in write-ahead logs and in partially written pages, so a copy taken at an arbitrary moment can capture a torn state. Restoring it may work, may require crash recovery, or may fail outright — and you will only find out during an incident.

A SQL dump avoids this. The database itself writes out a consistent snapshot of its contents. This guide shows how to schedule such a dump with a cronjob, so that a restorable copy always sits in your volume when the project backup runs.

How this works

The setup consists of three pieces that build on each other:

  1. A cronjob runs a dump command inside your database container on a schedule.
  2. The dump is written to a volume as a compressed .sql.gz file. Old dumps are deleted after a retention period.
  3. The mittwald project backup picks up that volume as part of its regular run, so the dump is retained off-site along with everything else in the project.

You therefore end up with two layers: the project backup restores the volume, and the dump inside it restores the database.

Prerequisites

To follow this guide, you will need:

  • A mittwald project on a plan that supports containerized workloads
  • A running PostgreSQL, MariaDB or MySQL container
  • For the CLI and API paths: the mittwald CLI (mw) installed and logged in (see the CLI documentation), plus the stack ID and service name of the container (mw stack list and mw container list show both). Everything up to and including verification also works entirely in the mStudio UI.

Step 1: Where the dump is stored

The dump goes into a subdirectory of the volume that already holds your database data. That volume is picked up by the project backup, so the dump is carried off-site with it — no additional volume and no stack change required.

This is also what the container templates do:

  • PostgreSQL: /var/lib/postgresql/backups
  • MariaDB and MySQL: /var/lib/mysql/backups

Step 2: Choose the dump command

The commands below write a timestamped, gzipped dump and delete dumps older than seven days. They abort without leaving a truncated file behind if the dump fails, so a failed run never overwrites your last good backup with a broken one.

Adjust the d= path if your volume is mounted somewhere else (see step 1).

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 dumps the entire cluster, including all databases and roles. It authenticates over localhost without a password, because the official images grant trust for local TCP connections in their generated pg_hba.conf.

To dump a single database instead, replace pg_dumpall with pg_dump -h localhost -U "$POSTGRES_USER" -d "$POSTGRES_DB".

Step 3: Create the cronjob

  1. Navigate to the project that your database container runs in.
  2. Select "Cronjobs" from the "Components" section of the sidebar.
  3. Click "Create".
  4. Enter a name and switch the target from "App" to "Container".
  5. Pick your database container under "Linked container".
  6. Paste the dump command from step 2 into "Command to execute".
  7. Leave the interval on "Cron syntax" and enter a schedule, for example 0 3 * * * for a daily run at 03:00. Set the time zone to Europe/Berlin.
  8. Click "Create".

The dialog has no field for failure notifications. Configure them afterwards: open the cronjob, and use "Edit" in the "Error handling" section to set the timeout and the email address. By default, mittwald notifies you from the first failed run onwards.

Schedule the dump so that it finishes before your project backup runs, and outside of peak traffic hours.

Step 4: Verify the backup

A backup you have never tested is an assumption, not a backup. Trigger the cronjob once manually and check the result.

  1. Open the cronjob and click "Run now" in the "Interval" section.
  2. Switch to the "History" tab. The run appears with its date and runtime.
  3. A failed run is marked "Execution failed". Open the "..." menu on that entry and choose "Show log" to see the exit code and the error output.

The cronjob overview marks failing cronjobs with the same "Execution failed" badge, so a glance at that list tells you whether your backups are still running. Make it a habit to look — a backup cronjob can fail for weeks without anything else going wrong.

A runtime of 0 seconds together with a failed status usually means the dump never connected to the database.

Repeat this check after any change to the database version, the volume layout or the dump command.

Restoring a dump

Restore the volume from the project backup first if the container itself is gone, then replay the dump into the running database.

A restore runs a command inside the container, which the mStudio UI does not offer on its own. You have three ways in:

  • Web-SSH — install the free 1-Click Web-SSH for Apps & Containers extension from the marketplace. It gives you a terminal for apps and containers directly in mStudio, without a local CLI or SSH keys. The same terminal lets you inspect the dump files in the volume.
  • CLImw container ssh <container-id> opens a shell in the container.
  • Any SSH clientmw container ssh <container-id> --info prints the hostname and username without connecting, so you can use OpenSSH or any other client: ssh <username>@<hostname>.

All three drop you inside the container. List the available dumps first — for MariaDB and MySQL, the directory is /var/lib/mysql/backups (see step 1):

user@container $ ls -la /var/lib/postgresql/backups

Then replay the one you want:

user@container $ gunzip -c /var/lib/postgresql/backups/backup_2026-09-07_030000.sql.gz | psql -h localhost -U "$POSTGRES_USER" -d postgres

A pg_dumpall dump recreates roles and databases. When you replay it into a cluster where those already exist, psql reports already exists errors for them and continues — the table data is still restored. For a clean restore, drop and recreate the target database first, or restore into an empty container.

Because these dumps include the role definitions (PostgreSQL) and the mysql system database (MariaDB and MySQL), database users and their passwords are restored along with the data.

Further resources