VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Guides
Launch
Go-live checklist
Make your first App Store purchase
Make your first Google Play purchase
Troubleshoot
Troubleshooting
Recover from incidents
SDK error codes
Purchase error codes
API error codes
FAQ
Recipes
Win back subscribers
Handle plan changes
Unlock the app after a web purchase
Link accounts across platforms
Handle refunds
Register devices for push
Migrate
Migrate to Voidhash
Migrate from RevenueCat
Migrate from Adapty
Migrate from Superwall
Run in observer mode
Privacy and hosting
Delete personal data
Self-hosting
Operate a self-hosted server

Operate a self-hosted server

Upgrade a self-hosted install, back up its data, set up analytics users, and rotate its secrets.

A self-hosted install keeps its state in four services: PostgreSQL, ClickHouse, Redis and S3-compatible storage. This page covers upgrading it, backing it up, setting up its analytics users and rotating its secrets. To set one up, see Self-hosting.

Alpha

Self-hosting isn't yet supported for production. There are no versioned server releases, no container images and no upgrade guarantees between commits.

Upgrade

The server has no release versions or changelog yet: you upgrade by moving to a newer commit of main, which is also the only line that receives security fixes. The SDKs, server libraries and CLI are versioned separately; see Releases.

Database migrations only move forward. There are no down migrations, and a migration can remove tables or columns the previous commit still uses. Upgrades therefore aren't zero-downtime, and going back means restoring a backup.

Back up PostgreSQL

Take a backup first; see Back up. It is your only way back.

Stop the API and workers

Stop every API and worker process, so no old code runs against the new schema.

Update the source

git pull
pnpm install --frozen-lockfile

Rebuild Studio as well: it reads AUTH_PROVIDER when it is built.

Apply the migrations

On an evaluation install, pnpm dev applies pending migrations every time it starts. Against any other server, run:

pnpm db:migrate --force

It applies each pending migration in its own transaction and records it in the __alchemy_migrations table. It reads the connection from DATABASE_HOST, DATABASE_PORT, DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD and DATABASE_SSL, not from the BACKEND_DATABASE_* settings the API uses, and refuses a non-local host without --force. Run it once, from one machine; it takes no lock.

Start the API and workers

ClickHouse needs no command: each API and worker process applies its pending ClickHouse migrations before its first analytics read or write. The tables for background jobs in PostgreSQL are also created at start.

GET /health on the API answers {"status":"ok"} once the process serves requests. It doesn't check the database or other services.

Back up

StoreWhat it holdsBack up
PostgreSQLProjects, people, purchases, subscriptions, settings, encrypted credentials, sessions, and queued and scheduled work.Yes. It is the system of record.
ClickHouseAnalytics events and ad delivery, plus copies of PostgreSQL tables that reports join with.Yes. Events exist nowhere else. A daily check copies missing rows of the copied tables again.
S3-compatible storageUploaded files, project and paywall assets, compiled paywall builds and components.Yes. There is no tool to rebuild them. Skip the uploads/ prefix, which expires in two days.
RedisRate limits, analytics query quotas and caches.Optional. No purchase, person or workflow state lives there.

Also keep ENCRYPTION_KEY with your backups, but stored apart from them. Without it, the store, payment, push and webhook secrets in a PostgreSQL backup can't be read.

Use each store's own tools, such as pg_dump or volume snapshots for PostgreSQL and BACKUP for ClickHouse. Voidhash has no backup command of its own.

Run Redis with append-only persistence and the noeviction policy. If you lose its data, stop the API and workers, start Redis empty and start them again: rate limits and quotas start over, and caches refill.

Restore

Restore PostgreSQL, ClickHouse and the bucket from backups taken at about the same time, with the same commit of the source that wrote them, and the same ENCRYPTION_KEY.

Analytics users

Reports and VoidQL can read ClickHouse as two read-only users: a reader and an agent. Without them, reads run as the main user with the same per-query limits, but ClickHouse doesn't filter rows by project and doesn't apply the per-team VoidQL quota; the server logs a warning. VoidQL still limits every query to the project it runs in, since it adds that filter to each table it reads. To create them, run this from the checkout with your ClickHouse settings in the environment:

pnpm --filter @voidhash/backend-app analytics:clickhouse-access

It prints the SQL to run as a ClickHouse administrator, or runs it with --apply. For a user without a password in ANALYTICS_CLICKHOUSE_READER_* or ANALYTICS_CLICKHOUSE_AGENT_*, it generates one and prints the values to add to your configuration. Add --quotas=false if your administrator can't create quotas.

Rotate secrets

  • ENCRYPTION_KEY. Set the new key as ENCRYPTION_KEY, and the old one in ENCRYPTION_KEY_PREVIOUS (comma-separated if several). Restart the API and workers. Every 10 minutes a worker re-encrypts stored secrets with the new key and logs how many remain. When it logs that ENCRYPTION_KEY_PREVIOUS can be removed, remove it and restart again. A key set for the first time also encrypts secrets stored before as plain text.
  • VOIDHASH_AUTH_SECRET. Used only with AUTH_PROVIDER=local. Changing it signs everyone out; there is no overlap period.

Next steps

  • Self-hosting
  • Recover from incidents
  • Troubleshooting

Something wrong or missing on this page? Tell us by email or on Discord.

PreviousSelf-hosting

On this page

UpgradeBack up PostgreSQLStop the API and workersUpdate the sourceApply the migrationsStart the API and workersBack upRestoreAnalytics usersRotate secretsNext steps