Skip to content
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_KEY is the server's main secret. It's 32 random bytes in the .env settings 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

PlatformDatabaseWhen to choose it
Your own server with DockerSQLite in a Docker volume, or Postgres alongsideFull control, one server, the fewest outside dependencies
Cloudflare WorkersD1No server of your own. The free plan is enough to try it, a team needs Workers Paid
VercelPostgres on NeonYour team already runs on Vercel
Google Cloud RunPostgres on Cloud SQL or NeonYour infrastructure is in Google Cloud
AWS App RunnerPostgres on RDS or NeonOnly 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:

Shell
npm install
node scripts/server-init.mjs --origin https://vault.company.example

server-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:

Shell
node scripts/doctor.mjs https://vault.company.example

Every 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:

  1. Print or save the Emergency Kit the web app shows.
  2. Create a recovery code and turn on a second factor.
  3. Set up backups and alert channels.

Settings reference

VariableWhat it isRequired
SERVER_KEYthe server key, 43 base64url charactersyes
SERVER_KEY_PREVIOUSthe previous key during a rotationno
BOOTSTRAP_TOKENone-time key for the first ownerno
PUBLIC_ORIGINthe public address, without a trailing /yes
DATABASE_URLPostgres address. Takes priority over SQLITE_PATHVercel, Cloud Run, App Runner
SQLITE_PATHSQLite file for your own serverwithout DATABASE_URL
TRUST_PROXYwhere the client address comes from, see belowno
AUDIT_RETENTION_DAYShow long to keep the audit log: 0 for forever, otherwise 30 to 36,500. Default 365no
RECOVERY_DELAYwait before a recovery code releases keys. Default 1hno
RESEND_API_KEY, ALERT_EMAIL_FROMemail for alerts and invitation codes, sent through Resendno
STATUS_TOKENoperator token for metrics and doctorno
CRON_SECRETsecret for scheduled cleanup on VercelVercel

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.