Managing secrets and environment variables in Codex Sites
Where each value belongs in Codex Sites — runtime secrets in the panel, binding names in hosting.json, keys in .env — and why you redeploy after a change.
If you are wiring secrets into a Codex Sites app, the rule is short: runtime environment values and secrets live in the Sites panel, never in .openai/hosting.json and never committed to your repository. That hosting file only records project linkage and the names of storage bindings. You keep a local .env and .env.example aligned with the keys your code needs for local development, and the moment you add, update, or remove a hosted value, you ask Codex to redeploy the approved saved version so the live site reads the new configuration. Three places, one of which holds secrets — get that straight and the rest is discipline, not guesswork.
Codex Sites is in preview, so we will be honest where the docs are quiet: if a detail is not documented yet, we say so rather than invent it.
Three places values live (and which holds secrets)
The confusion that leaks credentials is treating "config" as one bucket. It is three, and only one of them is for secrets. Here is the mental model we keep in front of us, mapped to where each kind of value actually belongs.
| Kind of value | Where it belongs | Committed to Git? | Holds secrets? |
|---|---|---|---|
| Runtime environment variables and secrets (API keys, tokens, connection strings) | The Sites panel | No — never | Yes |
Project linkage (project_id) and storage binding names (d1, r2) | .openai/hosting.json | Yes — it is safe to commit | No |
| Keys your code reads during local development | Local .env (real values) and .env.example (placeholders) | .env.example yes; real .env no | Real .env only, locally |
Read it top to bottom and the line is clear: the Sites panel is the vault, .openai/hosting.json is a map, and your .env files are a local convenience that mirror the shape of your config without ever carrying the production values. Anything secret that is destined for the hosted site has exactly one home — the panel.
Set hosted env values in the Sites panel
To configure the values your deployed site reads at runtime, open Sites in the app sidebar and select your project. The Sites panel is where you add, update, or remove hosted environment variables and secrets. That is the whole interface for it: one place, scoped to the project, where the deployment picks up its configuration.
This is deliberate. Because the panel is separate from your source, secrets stay out of files that get committed, diffed, and shared. An API key, a database connection secret, an auth token — each is added in the panel, and the hosted deployment reads it at runtime. You are not pasting it anywhere a teammate will later git log their way into. If a more granular detail about the panel is not in the official docs, treat it as undocumented for now rather than assuming behavior — the feature is in preview, and the panel is the documented surface for these values.
Keep .env and .env.example aligned for local dev
Local development still needs the keys your code references, and that is what .env is for on your machine. The discipline the docs ask for is alignment: keep your local .env and a checked-in .env.example carrying the same key names the app needs, so nobody cloning the repo has to guess which variables exist.
The split is simple. .env.example lists every key with a placeholder value and gets committed. The real .env carries the actual local values and stays out of version control. A minimal .env.example looks like this:
# .env.example — key names only, no real values. Safe to commit.
DATABASE_URL="postgres://user:password@localhost:5432/dbname"
THIRD_PARTY_API_KEY="your-key-here"
SESSION_SECRET="generate-a-random-value"
Your real .env mirrors those keys with genuine values and is git-ignored:
# .env — real local values. DO NOT COMMIT.
DATABASE_URL=postgres://app:s3cr3t@localhost:5432/oran_local
THIRD_PARTY_API_KEY=sk_live_redacted_for_this_example
SESSION_SECRET=4f9c...redacted
Crucially, these files are for local development. They are not how the hosted site gets its secrets — the Sites panel is. Aligning the two .env files just means your local environment and your hosted configuration agree on which keys exist.
Redeploy after you change a value
Here is the step people miss, and it is the one that makes a config change actually take effect. When you add, update, or remove a hosted environment value in the Sites panel, the running site does not pick it up on its own. You ask Codex to redeploy the approved saved version, and the next deployment uses the updated configuration.
So the loop is: change the value in the panel, then redeploy the saved version you have already reviewed. Until you redeploy, the live site keeps serving whatever values were in place at its last deployment — which is exactly why a rotated key or a freshly added secret can appear to "not work" when really it just has not shipped yet. Treat "I changed a secret" and "I redeployed" as two separate actions.
What NOT to put in hosting.json
It is worth being blunt about the file that tempts people: .openai/hosting.json. This file holds the project linkage — the project_id — and optional storage binding names such as d1 and r2. It does not hold secret values, and it must not. A project with a database binding named DB and no file storage looks like this:
{ "project_id": "<project-id>", "d1": "DB", "r2": null }
That is the entire job of the file: say "this project is linked, and it has a D1 binding called DB." The <project-id> above is a literal placeholder — your real file carries the id Sites assigned. Notice there is nowhere in that shape for an API key, because there should not be. hosting.json is committed to your repository, so any secret pasted into it is a secret published to everyone with repo access. If you ever feel the urge to drop a key in here, stop: that is the Sites panel's job. (For the storage side of this file — when d1 and r2 get filled in and why — see adding a database and file uploads to a Codex Sites app.)
A secrets checklist before you share
Every Codex Sites deployment URL is production — there is no throwaway preview link — so reviewing before you share is the safety gate, and secrets are part of that review. Before a link leaves your hands, we run down this list:
- Every runtime secret is configured in the Sites panel, not in source.
.openai/hosting.jsoncontains onlyproject_idand binding names — no keys, tokens, or connection secrets..env.exampleis committed with key names and placeholders; the real.envis git-ignored and never staged.- A quick scan of the diff shows no real secret values about to be committed.
- After any change to a hosted value, you redeployed the approved saved version so the live site reads it.
That last point ties the whole thing together: review-before-share includes confirming you set runtime secrets through Sites and did not commit them in source. Pair this checklist with Codex Sites access control — secrets handling decides what your site can reach, access control decides who can reach your site, and you want both settled before the URL goes anywhere.
Pressure-test the brief before Codex touches it
The weak link is rarely the deploy — it is the brief. A fuzzy idea of which secrets a feature needs, or which values are genuinely runtime versus local-only, produces a config you have to untangle after the fact, and because every deployment is production, that costs you a real deploy. So before we hand a project to Codex, we draft the env-and-secrets plan the way we would any spec: which keys are runtime, which are local, what rotates, and what should never appear in a file at all. We do that drafting on oran.chat, asking GPT, Claude, and Gemini the same brief and branching the conversation instead of overwriting it, so the plan we hand Codex is the one that survived three sets of objections. If you want one instruction set to behave consistently across those models while you pressure-test, see the system prompt that works across GPT, Claude, and Gemini.
Once the plan is clear, the rest is the prompt-save-deploy loop covered in deploying an existing project to Codex Sites. The authoritative reference is the Codex Sites documentation, and since the feature is in preview, treat those pages as the live source of truth over anything here. For more like this, see Playbooks.