Environment Variables
Org admins configure secret sources; each app's .env.example declares which keys it actually uses. The Leash CLI syncs that list automatically on init, deploy, and dev, and the platform narrows the resolved values down to your manifest at runtime.
The model: org sources + .env.example manifest
A source is where secret values live (Doppler, 1Password, Bitwarden, Infisical, an HTTPS webhook of your own, or values you paste directly into Leash). Sources live at the org level — once. Each app declares which of those keys it needs via its .env.example file.
- Org owners configure sources at /dashboard/organization/secrets.
- App repos commit a
.env.examplelisting every env var the app reads (placeholder values are fine; the file is parsed for keys only). - On every
leash init,leash deploy, orleash dev, the CLI POSTs the parsed key list to the platform — the running app gets a runtime env containing exactly those keys. leash devpulls the same values for local dev — no.env.localfile to share.
Why .env.example?
The repo already knows which env vars it consumes — the names live in code or in an existing .env.example. Letting that file be the manifest means there's nothing to maintain in two places, and adding a new env var is a normal PR review.
Local dev with `leash dev`
After leash init (which writes .leash/config.json — commit it, and commit your .env.example too), everyone on the team runs:
$ leash dev
✓ Synced 4 required keys from .env.example
✓ Resolved 4 secrets from 1 source
Starting `npm run dev` with 4 secrets injected...
> next dev
ready started server on 0.0.0.0:3000
Pass an explicit command with --: leash dev -- npm run start. Use leash secrets types to generate a TypeScript declaration that types process.env from your manifest.
Multi-source collisions
If two org sources both define the same env var, the most recently updated source wins (last-write-wins). The org secrets dashboard surfaces a warning banner listing which keys collide and which source is currently winning, so admins can resolve by removing the duplicate or retiring a source.
Required env vars in the dashboard
Each app's detail page has a read-only Required env vars section showing the keys synced from .env.example. Each key carries a status badge:
- available — at least one active org source defines this key.
- missing — no org source defines this key; the app will boot without a value.
- unknown — the source kind doesn't expose its key list (e.g. token-backed providers). Collisions here surface at sync time, not in this view.
Encryption
Values are encrypted at rest in GCP Secret Manager under a Cloud KMS CMEK key. By default the key is held by Leash. Org owners can bring their own KMS key via the wizard at /dashboard/organization/security/encryption/cmek — paste a key resource name, Leash runs an encrypt+decrypt round-trip to validate the binding, and from that point on all new writes use your key. Revert to Leash-held at any time.
Audit log
Every source create/edit/revoke, every required-keys update, and every resolved-values pull writes a row at /dashboard/organization/security/audit. Owners can scan the log for anything unusual.
Values injected at container start
Secrets are baked into the Cloud Run revision at deploy time via secretKeyRef mounts. Rotating a value at the source updates Secret Manager but the running revision keeps the old value until the next deploy (or sync-now in the dashboard).