Skip to main content

Backup, Restore, and Recovery

This guide applies to the supported single-host production Compose deployment. The repository ships guarded commands for local backups, restoration, scheduling, encrypted off-site copies, and freshness monitoring.

What must be protected

DataLocationImpact if lost
Forum databaseneo4j-data Docker volumeForum content and configuration are lost
Auth0 sessionsfrontend-data Docker volumeUsers are signed out
Production secrets.env.productionServices cannot be recovered with the same identities and encryption keys
Uploaded objectsConfigured GCS bucketsImages and downloadable files are lost

The backup command protects the two Docker volumes. It deliberately excludes .env.production; store that file and its secrets separately in encrypted secret storage. Protect object-storage data with bucket retention, versioning, or a separate provider backup policy.

Create a cold backup

Create a protected destination and run the repository command from the deployment checkout:

sudo install -d -m 0700 -o "$(id -un)" -g "$(id -gn)" \
/var/backups/multiforum

scripts/backup-self-hosting.sh \
--env-file .env.production \
--output-dir /var/backups/multiforum

The command:

  1. validates the production Compose model and expected volumes;
  2. requires the database, backend, and frontend to be running;
  3. stops the write path and database;
  4. archives Neo4j and frontend sessions;
  5. writes a manifest containing selected image versions and SHA-256 checksums;
  6. restarts the services even if archiving fails; and
  7. publishes the completed bundle atomically.

The site has a brief outage while the volumes are copied. Each completed directory contains:

manifest.json
neo4j-data.tar.gz
frontend-data.tar.gz

Optional count-based local retention runs only after a new backup completes and the services restart:

scripts/backup-self-hosting.sh \
--env-file .env.production \
--output-dir /var/backups/multiforum \
--retention-count 7

Only complete directories with the exact generated timestamp format are eligible for pruning. Failed or incomplete backups do not trigger retention.

Schedule daily backups

The AWS Terraform example stages, but does not enable, a systemd service and timer. First complete a successful manual backup. Then review /etc/multiforum/backup.env and enable the schedule:

sudo systemctl start multiforum-backup.service
sudo systemctl enable --now multiforum-backup.timer
systemctl list-timers multiforum-backup.timer

The timer is persistent and adds a randomized delay of up to 30 minutes. Review recent runs with:

systemctl status multiforum-backup.service
journalctl -u multiforum-backup.service --since "2 days ago"

A scheduled local copy is not an off-site backup.

Encrypt and upload with Restic

The Terraform host includes Restic and stages an inactive systemd drop-in. Copy and restrict the example configuration:

sudo install -m 0600 \
/etc/multiforum/restic.env.example \
/etc/multiforum/restic.env
sudo install -m 0600 /dev/null /etc/multiforum/restic-password
sudoedit /etc/multiforum/restic.env
sudoedit /etc/multiforum/restic-password

Set RESTIC_REPOSITORY, add credentials required by the selected Restic backend, and choose a tag unique to the instance. Store the Restic password somewhere separate from both the server and repository.

Initialize the empty repository once:

sudo bash -c '
set -a
source /etc/multiforum/restic.env
set +a
restic init
'

Test an upload manually:

sudo bash -c '
set -a
source /etc/multiforum/restic.env
set +a
/opt/multiforum/scripts/upload-self-hosting-backup.sh \
--backup-root /var/backups/multiforum \
--keep-daily "$MULTIFORUM_BACKUP_RESTIC_KEEP_DAILY" \
--tag "$MULTIFORUM_BACKUP_RESTIC_TAG"
'

The command verifies the newest bundle's manifest and checksums before remote access. It uploads the bundle encrypted, then applies tag-scoped remote retention. A failed upload never runs retention.

After a successful test, activate the staged drop-in:

sudo install -m 0644 \
/etc/systemd/system/multiforum-backup.service.d/offsite.conf.example \
/etc/systemd/system/multiforum-backup.service.d/offsite.conf
sudo systemctl daemon-reload
sudo systemctl start multiforum-backup.service

The off-site upload runs only after the local backup succeeds. A remote failure marks the service failed without deleting the new local bundle.

Monitor freshness and integrity

Check a local bundle:

scripts/check-self-hosting-backups.sh \
--backup-root /var/backups/multiforum \
--max-age-hours 36

Require recent local and off-site protection and emit compact JSON:

sudo bash -c '
set -a
source /etc/multiforum/restic.env
set +a
/opt/multiforum/scripts/check-self-hosting-backups.sh \
--backup-root /var/backups/multiforum \
--max-age-hours 36 \
--restic-tag "$MULTIFORUM_BACKUP_RESTIC_TAG" \
--offsite-max-age-hours 48 \
--json
'

The check exits nonzero when local or remote protection is missing, stale, corrupt, future-dated, or unreachable. Send its result to a monitoring agent and alert when the check does not run. Also use an external uptime check, since the Multiforum host cannot report its own outage.

Restore a cold backup

Restore on a separate host first whenever possible. Preserve the original environment file and create a fresh safety backup before replacing current data. Inspect the bundle's recorded images:

jq .images /var/backups/multiforum/BACKUP_DIR/manifest.json

Configure the recorded Neo4j image. Neo4j stores are not portable across arbitrary database versions. Stop the application and database:

docker compose \
--env-file .env.production \
-f docker-compose.yml \
-f docker-compose.production.yml \
stop frontend backend

docker compose \
--env-file .env.production \
-f docker-compose.yml \
-f docker-compose.production.yml \
stop database

Run the guarded restore with explicit destructive confirmation:

scripts/restore-self-hosting.sh \
--backup-dir /var/backups/multiforum/BACKUP_DIR \
--env-file .env.production \
--confirm-replace-existing-data

The command verifies the manifest, checksums, archive paths, stopped-service precondition, and configured Neo4j image before replacing volumes. It never starts application services. After success, validate, start, and verify:

docker compose \
--env-file .env.production \
-f docker-compose.yml \
-f docker-compose.production.yml \
config --quiet

docker compose \
--env-file .env.production \
-f docker-compose.yml \
-f docker-compose.production.yml \
up -d

scripts/verify-self-hosting.sh --env-file .env.production

Use --allow-database-image-mismatch only after reviewing Neo4j compatibility. Restoring the original Auth0 session secret preserves the ability to decrypt restored sessions; otherwise users may need to sign in again.

Restore from Restic

On a recovery host with the Restic password and provider credentials:

sudo install -d -m 0700 /var/tmp/multiforum-restic-restore
sudo bash -c '
set -a
source /etc/multiforum/restic.env
set +a
restic restore latest \
--tag "$MULTIFORUM_BACKUP_RESTIC_TAG" \
--target /var/tmp/multiforum-restic-restore
'
sudo find /var/tmp/multiforum-restic-restore -name manifest.json -print

Locate the bundle containing the manifest and use it with the guarded restore command. Securely remove the temporary restore tree afterward.

Recovery drills

At a regular interval:

  1. Restore the newest remote snapshot onto a separate host.
  2. Start Multiforum with the recorded image versions.
  3. Run production verification.
  4. Complete an Auth0 login and representative read/write operations.
  5. Record recovery time and any manual steps.
  6. Run restic check and investigate failures immediately.

A backup that has never been restored is not yet a proven recovery plan.