Secrets
How leash dev and leash deploy turn the keys in your repo's .env.example into a runtime environment — locally and on the deployed service.
Declaring required secrets
Leash injects only the env keys you explicitly declare. List them in your project's .env.example:
# Required env keys for this appHUBSPOT_API_KEY=LINEAR_API_KEY=SLACK_BOT_TOKEN=
When you run leash dev, the CLI reads this file, matches the keys against secrets you've configured in the dashboard, and injects matches into your local process. Keys you don't declare are not injected — keeping your local env explicit and auditable.
When you run leash deploy, the same file drives which secrets are mounted on the deployed service.
Commit .env.example to git
The file is the manifest the CLI uses on every machine. Empty values are fine — Leash only reads the key names. Never commit .env or .env.local.
How leash dev resolves a secret
Each key in .env.example takes this path:
.env.example (your repo, the manifest)
|
v
leash dev / leash deploy (CLI parses key list)
|
v
POST /api/apps/<id>/secrets/sync (platform matches keys -> sources)
|
v
org-level secret sources (Native, Doppler, 1Password, GCP SM, ...)
|
v
process environment (injected into your dev process or
mounted on the Cloud Run revision;
read via process.env, os.environ,
os.Getenv, ENV[], etc.)See it run:
$ leash dev
✓ Synced 3 required keys from .env.example
✓ Resolved 3 secrets from 1 source
Starting `npm run dev` with 3 secrets injected...
TypeScript only: generate a declaration that types process.env from your manifest.
$ leash secrets types
✓ Wrote leash-secrets.d.ts (3 keys)
Secret scoping
Secret sources are configured once per organization by an admin (one HubSpot OAuth, one Linear API key, etc.). All apps in your org can request those secrets — but each source carries a per-app allow-list of which apps see it.
The dashboard URL /dashboard/apps/<id>/secrets is the per-app grant view: “which org-level secrets does THIS app see?” The actual credentials live at the org level at /dashboard/organization/secrets.
| Concept | Where it lives | Who configures it |
|---|---|---|
| Source (the credentials) | /dashboard/organization/secrets | Org owner |
| Per-app grant | /dashboard/apps/<id>/secrets | App owner / org owner |
| Required keys | .env.example in repo | Anyone with commit access |
Wire once, reuse everywhere means the source-level wiring (OAuth, API key, KMS-encrypted value) happens once at the org. Each app then declares which keys it needs via .env.example and is granted access by an admin.
Common issues
- No
.env.example, no injection.leash deverrors with a hint to create the file. Add it (even with empty values) and re-run. - Key declared but missing on the deployed app. The org has no source defining that key, or the app isn't in the source's allow-list. The required-keys section of the app detail page surfaces both.
- Rotated value not picked up. Secrets are baked into the Cloud Run revision at deploy time. Re-run
leash deploy(or click sync-now in the dashboard) to roll the running revision.
For the full org-level model — sources, encryption, audit log — see Environment Variables. For the full leash dev command reference (flags, auto-detection, sample output), see leash dev.