MyGround does not copy a running database's files when it backs up an app. For six of its 40 catalog apps (Immich, Nextcloud, Paperless-ngx, Firefly III, Kaneo and Penpot) it asks the database container for a dump with pg_dumpall or mariadb-dump, puts that dump into the restic snapshot instead of the raw data directory, and replays it on restore. For the apps that keep their data as plain files or in SQLite, it copies the files. This post shows how that works in the code, which apps it covers, and which ones it doesn't.
Everything below was read from the MyGround repository at commit 3209dcf (version 0.1.112) on 2026-11-10. I read the code and counted the catalog files; I did not run a dump and restore on a live server for this post, so treat the behavior descriptions as what the source says, not as a test report.
Why a database needs a dump instead of a file copy
A backup tool that copies /var/lib/postgresql/data while Postgres is running can capture files from different moments: a table file written before a transaction and the write-ahead log from after it. A logical dump asks the database itself for a consistent view and writes it as SQL, which any later version of the same database can read. That is the reason MyGround's catalog has a db_dump block for the apps that have a SQL database.
The 40-app catalog, counted
Each app in MyGround is a pair of files in the apps/ directory: a Docker Compose file and a TOML manifest with its name, health check, storage volumes and any extra settings. ls apps/*.toml | wc -l gives 40 on the commit above. Seven of them ship a SQL or document database container of their own. Six of those seven carry a db_dump entry:
| App | Dump command |
|---|---|
| Immich | pg_dumpall -U postgres |
| Paperless-ngx | pg_dumpall -U paperless |
| Kaneo | pg_dumpall -U kaneo |
| Penpot | pg_dumpall -U penpot |
| Nextcloud |
mariadb-dump of the nextcloud database |
| Firefly III |
mariadb-dump of the firefly database |
The seventh is Anytype, whose manifest describes one data volume holding MongoDB, Redis, MinIO and the any-sync nodes together. It has no dump entry, so its files are copied as they are. Five more apps (Groceries, Agentboard, Karakeep, Nomad and Headscale) keep a SQLite file in their data volume and are copied as files too.
You can check this on your own copy of the repository with grep -l db_dump apps/*.toml, which lists exactly the six manifests above.
What happens during a backup
A backup job loops over the app's storage volumes. For a volume without a dump entry, MyGround starts a restic container, mounts the volume's host path read-only at /data and runs restic backup with the tag <app>/<volume>. For a volume that has one, the code in src/backup.rs does this instead:
- It takes a process-wide lock, so only one database dump runs at a time.
- It runs
docker exec <container> sh -c "<command> > /tmp/<dump file>", so the dump is written inside the database container. - It copies the file out with
docker cpinto the app'sdumpsdirectory on the host, and removes the temporary file inside the container. - It backs up that
dumpsdirectory with restic under the volume's tag. - It deletes the dump file from the host once restic finishes.
Two consequences follow. The snapshot for the Immich postgres volume contains immich_db_dump.sql and not the Postgres data directory, so it is small and doesn't depend on the Postgres on-disk format. And if the dump command fails, the job fails: the loop stops with the error and the job is marked failed, instead of quietly falling back to a copy of the raw files. A broken dump shows up as a failed job in the dashboard on the day it happens, which is when you want to know.
What happens during a restore
Restoring a database snapshot is a different path from restoring files. The restore reports its progress in phases named extracting, wiping, importing and restoring. MyGround first extracts the snapshot with restic into a temporary directory and looks for the dump file by name. Only then does it run the app's wipe_command, which for Immich is DROP DATABASE IF EXISTS immich;, copy the dump into the container and run the app's restore_command (psql -U postgres < /tmp/immich_db_dump.sql for Immich). All six database apps have both a wipe and a restore command. The wipe happens after the extraction, so a snapshot that can't be read fails before anything in the running database is touched. The documentation page asks for a confirmation before this, because it replaces all current data.
What this doesn't cover
- Apps outside the six. A SQLite file copied while the app is writing to it can in principle be caught mid-write. The catalog has no per-app dump for those five, and Anytype's MongoDB files are copied the same way. I haven't tested a restore of any of them, so I can't say how often that matters in practice.
- Restore of a different database version. The dumps are SQL, which travels across versions better than raw files, but I haven't tried restoring a Postgres 14 dump into a newer image.
What I'd tell someone setting this up
Turn on a backup job for each of the six database apps and look at the first snapshot in the Backups page: it should contain the .sql file, not a data directory. If you run Anytype or one of the SQLite apps, stop the app before an important snapshot, or use a destination you can restore from on a spare machine first. The backup documentation describes jobs, destinations and restore, and the S3 Glacier Deep Archive post covers what a cheaper destination costs.
MyGround is open source and installs with one command on a Linux or macOS machine. You can browse the catalog and the 40 manifests on myground.online.
Top comments (0)