Skip to content
Self-hosting

Backup and scaling

Choose the right data layout for one node or many, and make sure the review state can be restored.

Default: SQLite single node

SQLite is the default because it is operationally simple and good enough for a single maintainer instance. The tradeoff is obvious: if the volume is lost, review state is lost.

Do not treat the default data volume as a backup. Snapshot it or enable continuous backup.

Continuous backup with Litestream

.env
BACKUP_ACKNOWLEDGED=true
LITESTREAM_ACCESS_KEY_ID=<key>
LITESTREAM_SECRET_ACCESS_KEY=<secret>
LITESTREAM_ENDPOINT=s3.example.com
LITESTREAM_REGION=us-east-1
docker compose --profile litestream up -d
bash

Multi-instance: Postgres and Redis

Postgres
Use DATABASE_URL for a shared database and queue claiming with SKIP LOCKED semantics.
Redis
Use REDIS_URL for distributed rate limiting, webhook deduplication, and shared short-lived caches.
PgBouncer
Use the pgbouncer profile when many replicas need pooled database connections.
.env
POSTGRES_PASSWORD=<password>
DATABASE_URL=postgres://gittensory:<password>@pgbouncer:5432/gittensory
REDIS_URL=redis://redis:6379
QDRANT_URL=http://qdrant:6333
docker compose --profile pgbouncer --profile qdrant up -d
bash

One-time SQLite to Postgres copy

Existing SQLite installs can copy state into a fresh Postgres database with the bundled migrator. It dry-runs by default and only commits when --execute is present.

npm run selfhost:postgres:migrate -- --sqlite /data/gittensory.sqlite --postgres-url "$DATABASE_URL"
npm run selfhost:postgres:migrate -- --sqlite /data/gittensory.sqlite --postgres-url "$DATABASE_URL" --execute
bash

Restore checks

  • Restore to a separate host or volume, never over the live instance first.
  • Boot the app and confirm /ready returns 200.
  • Confirm migrations do not fail or reapply incorrectly.
  • Confirm recent review rows and job state are present.

After scaling, revisit Operations and Security because network and credential boundaries change.