Backups
Back up PostgreSQL, attachment storage, and installation secrets as one recovery set.
A complete backup has three parts:
- PostgreSQL: pages, revisions, collaboration snapshots, users, metadata, and audit history.
- The object store: the attachment bytes. A database-only restore leaves attachment links broken.
- The
.envfile: database, auth, and S3 credentials.
Take all parts close together and keep them as one recovery set. For a consistent raw-volume snapshot, use a short maintenance window and stop application writers first:
docker compose stop web server collabTake a database backup
docker exec nilovon-wiki-postgres pg_dump -U postgres nilovon-wiki \
| gzip > database-$(date +%F).sql.gzTake an attachment backup
With the bundled RustFS service, stop it before archiving its raw data volume, then restart the stack after both backup parts are complete:
docker compose stop rustfs
docker run --rm \
-v nilovon-wiki_nilovon-wiki_rustfs_data:/data:ro \
-v "$PWD":/backup \
alpine tar czf /backup/attachments-$(date +%F).tar.gz -C /data .
docker compose up -dFor an external S3 service, use that provider's versioned backup or object-copy tooling instead. Copy the backup set off the wiki host; a backup on the same machine is not disaster recovery.
Backups and retention are separate
Retention windows and deletion blocks govern the live database, not your archives. A dump taken before a purge still contains the purged rows, and a deletion block does not reach into a tarball. Two consequences worth deciding about deliberately:
- A restore from an old backup brings deleted data back. Where a deletion was a data-protection obligation, restoring is not the end of the task.
- Keeping backups longer than your audit window means the audit history you configured to expire still exists offline.
Align your backup rotation with those windows on purpose rather than by accident. See Retention, trash & deletion blocks.
Restore
Restore into a fresh stack. Never extract an archive over a running or non-empty RustFS volume.
Put the original .env in place first, then restore attachment storage:
docker compose down
docker volume rm nilovon-wiki_nilovon-wiki_postgres_data nilovon-wiki_nilovon-wiki_rustfs_data
docker volume create nilovon-wiki_nilovon-wiki_rustfs_data
docker run --rm \
-v nilovon-wiki_nilovon-wiki_rustfs_data:/data \
-v "$PWD":/backup \
alpine sh -c 'cd /data && tar xzf /backup/attachments-2026-07-09.tar.gz'
docker compose up -d postgres rustfs rustfs-initRestore PostgreSQL after it is healthy:
gunzip -c database-2026-07-09.sql.gz \
| docker exec -i nilovon-wiki-postgres psql -U postgres nilovon-wiki
docker compose up -dVerify several attachment downloads after the restore, not only the database health endpoint.
Automatic database dumps
docker compose --profile backup up -d backupThis writes nightly database dumps to nilovon-wiki_nilovon-wiki_backups and applies
BACKUP_KEEP_DAYS retention. It does not copy attachment bytes or move either volume
off-host; schedule those separately.
Keep credentials with the backup set
Preserve .env, especially BETTER_AUTH_SECRET, POSTGRES_PASSWORD, S3_ACCESS_KEY_ID, and
S3_SECRET_ACCESS_KEY. Without the original values, sessions may be invalid and the restored
object store may not start.