Wiki
Self-hosting

Backups

Back up PostgreSQL, attachment storage, and installation secrets as one recovery set.

A complete backup has three parts:

  1. PostgreSQL: pages, revisions, collaboration snapshots, users, metadata, and audit history.
  2. The object store: the attachment bytes. A database-only restore leaves attachment links broken.
  3. The .env file: 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 collab

Take a database backup

docker exec nilovon-wiki-postgres pg_dump -U postgres nilovon-wiki \
  | gzip > database-$(date +%F).sql.gz

Take 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 -d

For 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-init

Restore 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 -d

Verify several attachment downloads after the restore, not only the database health endpoint.

Automatic database dumps

docker compose --profile backup up -d backup

This 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.

On this page