Secrets and local development
How a secret reaches your published app, why it doesn't reach your laptop on its own, and the one line that fixes that.
Secrets and local development
A secret is a value your app needs at runtime but that must never sit in your code: an API key, a webhook signing secret, a third-party token. You give it to White Ghost once; your published app reads it as an environment variable from then on.
One secret, one value per app
Every app has exactly one value per secret. Your published app and every preview of it read the same secret — there is no separate "preview" or "staging" copy. The only thing that differs between your published app and a preview is the database: each preview gets its own copy of the Ghosty database, and the platform manages that connection for you.
Values are write-only. After you save a secret, neither the console nor the CLI will ever show it again. To change it, save a new value under the same name.
Where a secret lives
| Where | What's there | Who puts it there |
|---|---|---|
| The Ghosty platform | The real value, bound to your published app as MY_SECRET |
You, via ghosty secret or the console |
backend/.env.local on your computer |
The value your local run uses | You — the platform never writes to your computer |
That second row is the one people trip over. Saving a secret on the platform does not — and cannot — put anything on your laptop. Your local run only sees what's in backend/.env.local (which is ignored by git, so it never leaves your machine).
Adding a secret from the CLI
ghosty secret stripe-api-key
The CLI asks two questions, neither of which is ever echoed:
- Value — the real value, stored on the platform and live on your published app right away.
- Local value for development? — optional. Press enter to skip, or paste the value your local run should use (a test key, a stub). It's written to
backend/.env.local.
If you skip the second question, the CLI leaves an empty STRIPE_API_KEY= line in backend/.env.local so you know where the value goes.
For scripts and assistants: ghosty secret stripe-api-key --value "…" --local-value "…". Non-interactive runs never prompt.
Rotating a secret (ghosty secret stripe-api-key again) updates the platform value and never overwrites a local value you already have.
Adding a secret from the console
Open your app → Secrets, give it a name and a value, and save. It's live on your published app immediately. The console then shows you the exact line to add for local testing:
# backend/.env.local
STRIPE_API_KEY=<your local value>
Paste it into backend/.env.local yourself — the console can't reach your files.
ghosty dev tells you what's missing
Every time you run ghosty dev, it compares the secrets your app has on the platform with what's in backend/.env.local and prints a heads-up for each one with no local value:
! HEADS-UP: STRIPE_API_KEY has no local value in backend/.env.local — the deployed app has it, your local run won't.
It's only a reminder — your app still starts. Add the line and the warning goes away.
Reading a secret in your code
Always from the environment, the same way locally and in production:
// Node
const key = process.env.STRIPE_API_KEY;
# Python
import os
key = os.environ["STRIPE_API_KEY"]
Your app's assistant knows this rule; the add-secret skill in your app's folder walks it through the whole flow. To see every variable your app runs with, secrets included, see Your app's environment.
FAQ
Are secrets per environment? / ¿Los secretos están por ambiente?
No. Each app has one value per secret, shared by the published app and all its previews. Only the database differs per environment, and the platform manages that. If you need a different value locally (a test key, for example), that's what backend/.env.local is for.
I created a secret in the console and my local run can't see it.
Expected — the console can't write to your computer. Add MY_SECRET=<your local value> to backend/.env.local (the console shows the exact line after you save).
I ran ghosty secret and the line in .env.local is empty.
You skipped the "local value for development" question. Put a value after the =, or run ghosty secret <name> again and answer it.
Can I see a secret's value again? No. Values are write-only. Save a new value under the same name if you've lost it.
Where does DATABASE_URL come from?
The platform sets it — in production, in previews, and locally through ghosty dev. Never create it by hand.