Browse the docs
Self-hosting
Run Crunu on Google Cloud Run
Deploy the Crunu server to Cloud Run with Postgres and Secret Manager, set the client address correctly, and understand how cleanup works without a scheduler.
Last updated
On this page
On Google Cloud, the Crunu server runs on Cloud Run, keeps its data in Postgres on Cloud SQL or Neon, and stores its secrets in Secret Manager. Choose it if your infrastructure is already in Google Cloud. Read Self-hosting: overview and platforms first and prepare .env.
What you need
gcloud, signed in;- a Google Cloud project with billing;
- a Postgres database on Cloud SQL or Neon;
- the web app of the same version, prepared as described in Docker.
Deploy
sh deploy/gcp/deploy.sh --project my-project --region europe-west1 --service vaultThe secrets, such as SERVER_KEY, SERVER_KEY_PREVIOUS, BOOTSTRAP_TOKEN and DATABASE_URL, go into Secret Manager, and the service gets access only to them. A new secret version is created only when a value changes. The service references secrets by version number, not as latest, so each revision keeps the values it was deployed with. Rolling traffic back to an earlier revision brings back its secrets too.
For Cloud SQL through its socket, use an address of this form in DATABASE_URL:
postgres://user:password@/database?host=/cloudsql/project:region:instanceService variables are merged: what you set on the service by hand stays. Server variables that are no longer in .env are listed, and removed only with --prune. The secrets themselves stay in Secret Manager, because deleting them can't be undone. The script prints the delete command.
The client address
By default TRUST_PROXY is xff:1, for clients that reach the service directly at its run.app address or at a domain mapped to the service. If an external Google load balancer sits in front of the service, deploy with --trust-proxy xff:2. The service then accepts traffic only from the load balancer, and PUBLIC_ORIGIN must be the load balancer's address. The script refuses other values, because the server would see the wrong client address.
Don't proxy the domain through Cloudflare if you use network rules. The server would see Cloudflare's addresses instead of your clients'. Keep the DNS record as DNS only.
How cleanup works on Cloud Run
Cleanup deletes expired records, trims the audit log and raises alerts that depend on state. On Cloud Run it can't be triggered from outside on a schedule: the Node server has no route for an external scheduler such as Cloud Scheduler. Cleanup runs from a timer inside the process, once a minute, and along with requests, no more than once every 5 minutes per instance.
Cloud Run gives an instance CPU only while it handles requests by default, and with --min-instances 0, which the script sets, it stops an instance with no traffic. So cleanup runs only when there is traffic. Agents on your machines contact the server every minute by default, so with working machines, cleanup runs roughly every 5 minutes along with their requests. A pass may stretch until the next requests. With --min-instances 1 the instance stays up, but it still gets almost no CPU between requests.
Deadlines don't depend on cleanup: sessions, share links, break-glass access and the start of delayed network rule versions are checked on every request. These do depend on it:
- state-based alerts, such as expired machine leases, expiring certificates, outdated file copies and undelivered environment keys, appear only at the next cleanup;
- delivery retries to alert channels and the closing of some alerts wait for it;
- a machine pass used from outside and then from inside the allowed networks can go unnoticed, because a cleanup pass has to run while both uses are less than 10 minutes old;
- expired records are deleted and the audit log is trimmed later, so the database grows faster.
If these alerts matter to you, choose a platform with a scheduler: your own server with cleanup every minute, Cloudflare Workers every 5 minutes, or Vercel with a frequent --cron.
Old revisions
The deploy script doesn't tag revisions, so old revisions have no addresses of their own. If you tagged revisions by hand, remove the tags from old ones, because they run with the same database and secrets:
gcloud run services update-traffic vault --region europe-west1 --remove-tags <tag>Logs and rollback
- Logs: the Cloud Run console, your service, Logs.
- Rollback: list revisions with
gcloud run revisions list --service vault --region europe-west1, then send all traffic to the previous one withgcloud run services update-traffic vault --region europe-west1 --to-revisions <revision>=100.
A revision remembers its secrets. If you rotated the server key after that revision was deployed, roll back by deploying the previous commit with the current .env instead. Otherwise the old SERVER_KEY comes back and tokens already re-hashed with the new key stop working.
- Backups: export Crunu archives, through a Cloud SQL tunnel if the database has no public address, and keep Cloud SQL's automatic backups on as an extra layer. See Backups and restore.