Backup and Restore
A Pengin-Pi-3 project is four things, and a backup plan has to cover all four:
- The database — users, pages, wiki content, roles, history, and dynamic content
- Media — uploaded images, video, and files, if you store them locally
- Your code — every app, template, and change you've made on top of the core
- Your keys — the
.envfile
Lose the database and you lose the site's content. Lose the code and you lose your work. Lose the keys and you're rebuilding access to everything else. None of them can be recovered from the others.
The minimum
If you do nothing else on this page, do this:
- Export the database regularly and keep the copy on a separate backup drive
- Add a git remote on that backup drive and push your code to it
- Keep a copy of
.envsomewhere secure that you can find again
That's enough to recover from a dead laptop, a stolen machine, or a failed drive. Everything below is about doing it better.
Backing up the database
Relational data and FerretDB documents live in one PostgreSQL instance, but in two databases:
DB_NAME— Django's tables: users, slugs, wiki pages, roles, historyFERRETDB_DB(defaultpostgres) — FerretDB's document storage, which holds dynamic content-type data
Dumping only DB_NAME silently misses every dynamic content entry. Back
up the whole instance.
Under Docker
docker compose exec -T postgres pg_dumpall -U "$DB_USER" \
> backup-$(date +%F).sql
-T disables the TTY, which you need when redirecting output or running
from cron.
Compress it — SQL dumps shrink dramatically:
docker compose exec -T postgres pg_dumpall -U "$DB_USER" \
| gzip > backup-$(date +%F).sql.gz
Without Docker
pg_dumpall -h 127.0.0.1 -U penginpi > backup-$(date +%F).sql
A portable copy of documents
A pg_dumpall restores cleanly onto the same PostgreSQL and DocumentDB
versions. For a copy of the document data that doesn't depend on those
versions, export it through FerretDB as well:
mongodump --uri "mongodb://$DB_USER:$DB_PASSWORD@127.0.0.1:27017/$DB_NAME" \
--out documents-$(date +%F)
Worth doing before any FerretDB or DocumentDB version upgrade.
SQLite
A development instance running on the SQLite fallback is one file:
db.sqlite3 in the project root. Copy it while the server is stopped.
Before every upgrade
Take a fresh dump before any git pull that includes migrations. The
web container applies migrations automatically on start, and Django
migrations aren't reliably reversible. The dump is your undo.
Backing up media
Only needed if you store uploads locally. With FILE_STORAGE_BACKEND=s3,
uploads live in your S3 bucket and are protected by it — enable versioning
on the bucket if you want deleted files to be recoverable.
With local storage, uploads are in ./media in the project root. Copy it
to the backup drive.
Linux, macOS, WSL
rsync -av /opt/pengin-pi-3/media/ /mnt/backup/pengin-pi-3/media/
The trailing slashes matter: they copy the contents of media into the
destination rather than nesting a second media folder inside it.
Leave out --delete for backups. With it, a file deleted by accident on the
server is deleted from the backup on the next run too.
Windows
robocopy C:\pengin-pi-3\media E:\backup\pengin-pi-3\media /E
/E includes subdirectories. Avoid /MIR for backups for the same reason as
--delete — it mirrors deletions.
Media is never pruned by the application, so this directory only grows. See Configuration.
Backing up your code
The public mirror has the core. It doesn't have your work — your apps, templates, settings changes, and fixes exist only where you put them.
Version your project in git, and push it somewhere that isn't your working machine.
A remote on your backup drive
A git remote can be a plain directory. Create a bare repository on the backup drive:
git init --bare /mnt/backup/git/my-project.git
Add it as a remote and push:
cd ~/projects/my-project
git remote add
Pages Here
No sub-pages yet.
Page Info
Wiki: Docs
Created on Sep 21, 2026 by Stuart Anderson
Maintainers
| Editor | Last Activity |
|---|---|
| Stuart Anderson creator | Sep 21, 2026 |