Skip to content

OneUptime backup and restore

OneUptime is tier 1 because it holds canonical incident, acknowledgement, timeline, workflow, and audit state.

Protected state

State Protection
PostgreSQL Custom-format pg_dump
ClickHouse Built-in BACKUP DATABASE to a dedicated named volume
OneUptime secrets SOPS-encrypted .env.sops.yaml in Git
Exact runtime Digest-pinned Compose in Git
Redis Not backed up; cache and queues rebuild empty

ENCRYPTION_SECRET is required with the PostgreSQL restore. A database backup without the matching encrypted SOPS file is not a complete recovery set.

Create a logical backup

Run as root on infra-services:

cd /opt/homelab/services/oneuptime
bash scripts/backup.sh

The script:

  1. writes a PostgreSQL custom-format dump to backups/postgres/oneuptime-<timestamp>.dump;
  2. verifies the dump catalog;
  3. runs ClickHouse BACKUP DATABASE oneuptime to oneuptime_clickhouse-backups;
  4. writes a SHA-256 file and a manifest linking both artifacts.

The restic job runs this script as its pre-hook, then backs up the PostgreSQL staging directory and ClickHouse backup volume to the local repository and B2.

The script does not delete old local logical backups. Deleting or relocating backup artifacts requires explicit owner approval. Monitor staging growth and make an owner-approved pruning decision after restic/offsite verification.

Isolated restore drill

Run after a successful logical backup:

cd /opt/homelab/services/oneuptime
bash scripts/restore-drill.sh

The drill:

  • verifies the PostgreSQL dump checksum;
  • restores PostgreSQL into a temporary container and volume;
  • mounts ClickHouse backup artifacts read-only into a temporary ClickHouse container and volume;
  • restores the database under oneuptime_restore;
  • requires non-zero table counts in both datastores;
  • removes only temporary test containers and volumes.

It does not modify production databases or delete backup artifacts.

Restore from restic

export RESTIC_REPOSITORY=/mnt/infra-backups/homelab-restic/oneuptime
export RESTIC_PASSWORD_FILE=/etc/restic/password
restic snapshots
restic restore latest --target /tmp/restore-oneuptime

For offsite recovery:

source /etc/restic/b2.env
export RESTIC_REPOSITORY=b2:<bucket>:oneuptime
export RESTIC_PASSWORD_FILE=/etc/restic/password
restic restore latest --target /tmp/restore-oneuptime

Production recovery

  1. Stop ingress and app. Leave Redis disposable.
  2. Start empty PostgreSQL and ClickHouse containers at the exact backed-up versions.
  3. Restore the SOPS file and render compose.env; verify the original ENCRYPTION_SECRET is present.
  4. Restore PostgreSQL:
docker exec -i oneuptime-postgres \
  pg_restore --username oneuptime --dbname oneuptime \
  --clean --if-exists \
  </tmp/restore-oneuptime/opt/homelab/services/oneuptime/backups/postgres/<dump>
  1. Copy the restored ClickHouse backup set into the oneuptime_clickhouse-backups volume without deleting other sets.
  2. Restore ClickHouse:
docker exec oneuptime-clickhouse bash -c '
  clickhouse-client \
    --user "$CLICKHOUSE_USER" \
    --password "$CLICKHOUSE_PASSWORD" \
    --query "RESTORE DATABASE oneuptime FROM Disk('\''backups'\'', '\''<backup-name>'\'') SETTINGS async = false"
'
  1. Start the app with workflow/remediation integrations disabled.
  2. Verify users, projects, incidents, timelines, workflow runs, and encrypted provider values.
  3. Start ingress and run scripts/smoke-test.sh --live.
  4. Re-enable notification and remediation workflows only after review.

If restoring over an existing ClickHouse database, use an isolated temporary database first. Never issue DROP DATABASE or remove a backup volume without explicit owner approval.

Acceptance

  • PostgreSQL and ClickHouse backups share the same manifest timestamp.
  • The isolated drill reports non-zero table counts.
  • Restic has a local snapshot and tier-1 B2 copy.
  • The matching SOPS file decrypts on the recovery host.
  • The exact OneUptime images remain available.
  • Application readiness and incident history pass after restore.