Browse the docs
Self-hosting
Self-hosting: overview and platforms
Run your own Crunu server on Docker, Cloudflare Workers, Vercel or Google Cloud Run. What the server stores, the settings to get right, and how to choose.
Last updated
On this page
Crunu runs as a service at app.crunu.com, and you can also run the same server yourself. This section is for teams that host their own. If you use the cloud service, you can skip it.
Every platform runs the same server code with the same database schema. That's why you can move between them in either direction with one archive. See Move between platforms.
What the server holds
- The server is blind. Its database holds only ciphertext, public keys, signed documents and metadata: names of organizations, projects, environments and machines, email addresses and the audit log. It can't decrypt any variable, because the keys belong only to people and machines.
SERVER_KEYis the server's main secret. It's 32 random bytes in the.envsettings file. Sessions, share links and invitations are signed with it, and the secrets behind authenticator-app codes are encrypted with it. Losing it signs everyone out and breaks share links, but no environment data is lost. You can change it without losses, see Rotate the server key.- Pick the server address once. The public address,
PUBLIC_ORIGIN, is tied to sign-in signatures, share links, passkeys and the agents on your machines. A move to another platform keeps the same address. Only the DNS record changes. - The server applies schema migrations itself when it starts. There's no separate database upgrade command. Migrations only add tables and columns, which is why rolling back the code never requires rolling back the database.
Platforms
| Platform | Database | When to choose it |
|---|---|---|
| Your own server with Docker | SQLite in a Docker volume, or Postgres alongside | Full control, one server, the fewest outside dependencies |
| Cloudflare Workers | D1 | No server of your own. The free plan is enough to try it, a team needs Workers Paid |
| Vercel | Postgres on Neon | Your team already runs on Vercel |
| Google Cloud Run | Postgres on Cloud SQL or Neon | Your infrastructure is in Google Cloud |
| AWS App Runner | Postgres on RDS or Neon | Only in AWS accounts where App Runner is still available |
Prepare the settings file
You need a computer with Node.js 24 or newer and a copy of the Crunu repository:
npm install
node scripts/server-init.mjs --origin https://vault.company.exampleserver-init creates .env with mode 0600 and fills in three new secrets:
SERVER_KEY, the server key;BOOTSTRAP_TOKEN, the one-time key for creating the first owner;CRON_SECRET, used for scheduled cleanup on Vercel.
Save a copy of .env in a password manager right away. Without it, the server can't be restored. Never commit the file or send it through chat.
For platforms with Postgres, pass the database address without exposing the password in the process list or shell history: --database-url env:DATABASE_URL reads it from a variable, and --database-url - reads it from the first line of stdin. Encode /, ? and # in the password as %2F, %3F and %23.
Every deploy script checks .env with the same code the server uses at startup, and stops with a clear message if something is wrong.
Deploy, check, create the first owner
Deploy with the script for your platform, using the page linked in the table above. Then check the server from your computer:
node scripts/doctor.mjs https://vault.company.exampleEvery check should pass or be skipped. See Monitoring and doctor.
Then open the address in a browser, choose Create the first owner and enter the bootstrap key, the BOOTSTRAP_TOKEN value from .env. After the first owner exists, that key no longer works. Everyone else joins by invitation.
Right after that:
- Print or save the Emergency Kit the web app shows.
- Create a recovery code and turn on a second factor.
- Set up backups and alert channels.
Settings reference
| Variable | What it is | Required |
|---|---|---|
SERVER_KEY | the server key, 43 base64url characters | yes |
SERVER_KEY_PREVIOUS | the previous key during a rotation | no |
BOOTSTRAP_TOKEN | one-time key for the first owner | no |
PUBLIC_ORIGIN | the public address, without a trailing / | yes |
DATABASE_URL | Postgres address. Takes priority over SQLITE_PATH | Vercel, Cloud Run, App Runner |
SQLITE_PATH | SQLite file for your own server | without DATABASE_URL |
TRUST_PROXY | where the client address comes from, see below | no |
AUDIT_RETENTION_DAYS | how long to keep the audit log: 0 for forever, otherwise 30 to 36,500. Default 365 | no |
RECOVERY_DELAY | wait before a recovery code releases keys. Default 1h | no |
RESEND_API_KEY, ALERT_EMAIL_FROM | email for alerts and invitation codes, sent through Resend | no |
STATUS_TOKEN | operator token for metrics and doctor | no |
CRON_SECRET | secret for scheduled cleanup on Vercel | Vercel |
Without email configured, email addresses aren't verified when people accept invitations. Set it up if people outside your team create accounts on your server.
The client address matters
Rate limits and network rules depend on the address the server sees. Behind a proxy, TRUST_PROXY tells the server which header to trust: xff:1 behind Caddy, cloudflare:1 for Cloudflare in front of Caddy, xff:1 or xff:2 on Cloud Run, and nothing on Vercel. doctor with the operator token checks that the setting matches how requests actually arrive.