Backup
The most important thing to back up is the Postgres database, that’s where all user data lives. The media volume can usually be re-fetched from upstream, and static files are regenerated on every container start.
You should perform a test restore at least once, so you know the procedure works when you actually need it.
Database
Make a dump of the database:
# Stop the other services so the database isn't changed mid-export
docker compose stop web nginx cache celery_worker celery_beat
docker compose exec db pg_dumpall --clean --username wger > backup.sql
docker compose start
To restore from a dump:
docker compose stop
docker volume remove docker_postgres-data
docker compose up db
cat backup.sql | docker compose exec -T db psql --username wger --dbname wger
docker compose up
The PowerSync bucket-storage tables (powersync.* schema) are effectively
a cache rebuilt from the public.* wger tables and sync_rules.yaml.
Nothing in there is a primary source of truth, every row can be regenerated
via a fresh snapshot against the main wger database.
pg_dumpall above captures the schema automatically, so there is
nothing extra to do. If backup size matters (bucket_data scales
with users × sync-rule complexity), you can safely exclude it:
docker compose exec db pg_dump --username wger \
--exclude-schema=powersync --clean --create wger > backup.sql
On restore, PowerSync re-bootstraps the schema on next startup and takes a fresh snapshot. Clients silently re-sync their local SQLite from scratch.
Important
After restoring the database, drop the PowerSync replication slot and restart the service so it re-reads from a valid WAL position:
docker compose exec db psql -U wger -c \
"SELECT pg_drop_replication_slot(slot_name) \
FROM pg_replication_slots \
WHERE slot_name LIKE 'powersync_%';"
docker compose restart powersync
Skipping this leaves the slot pointing at a WAL position that no longer
exists, and PowerSync fails with cryptic operator does not exist errors
until it is re-created.
Media
If you haven’t uploaded your own exercise images, exercise videos or gallery images, you don’t need to back up the media volume, the contents can be re-downloaded from the upstream wger instance. Truncate the relevant tables and run the sync commands again:
docker compose exec db psql -U wger -c "TRUNCATE TABLE exercises_exerciseimage, exercises_exercisevideo;"
docker compose exec db psql -U wger -c "TRUNCATE TABLE nutrition_image;"
docker compose exec web python3 manage.py download-exercise-images
docker compose exec web python3 manage.py download-exercise-videos
If you do have uploaded media that needs to be preserved, such as gallery entries, consult these options for backing up Docker volumes:
Static files
The contents of the static volume are 100% generated and recreated on startup, no need to back up anything.