Environment variables
Environment variables hold an app’s plain configuration, such as an API’s base URL, a region or a feature switch. They belong to the app, not to a version. They’re stored as plain text that anyone who can deploy the app can read, so put credentials in secrets instead.
Set, list and remove variables
Section titled “Set, list and remove variables”jiayang env set acme/hello API_BASE=https://api.example.com REGION=euSet API_BASE, REGION on acme/hello. The live code sees the change within a minute or two. Builds bake in what they read, so if acme/hello is a site or its build uses them, run `jiayang deploy acme/hello` again.jiayang env list acme/helloAPI_BASE=https://api.example.comREGION=eujiayang env remove acme/hello REGIONRemoved REGION from acme/hello. The live code sees the change within a minute or two. Builds bake in what they read, so if acme/hello is a site or its build uses it, run `jiayang deploy acme/hello` again.Everything after the first = is the value, so a value can contain =. env set and env remove print names and not values, so their output is safe in a CI log. env list prints the values. env unset and env rm are other names for env remove.
The app’s Environment tab in the dashboard does the same.
Setting, changing, removing and listing variables takes an editor or owner of the app, or a workspace admin or owner. Each change is recorded in the audit log with the names it touched, without the values.
How your app reads them
Section titled “How your app reads them”| Kind | Where the values are |
|---|---|
| Worker | On env, as strings: env.API_BASE. |
| Container | In the process’s environment: process.env.API_BASE, os.environ["API_BASE"]. |
| Static site | Only in its build. |
Every build that jiayang deploy runs also gets the app’s variables in its environment.
When a change arrives
Section titled “When a change arrives”Setting or removing a variable redeploys the live version with the new values. Code that reads variables while it runs picks up the change without a new deploy:
- A Worker’s code sees the change once the new configuration reaches every location, which can take a minute or two. Until then, some requests still get the old values.
- A container restarts with the new values, from the same image, with no rollout. The change first has to reach Cloudflare’s locations, which can take a minute or two, and until then requests still reach the old container. The first request after that stops it and waits while the new one starts, so it can take as long as the app takes to start, or get a 503 saying the app is starting.
- A static site gets them only in its build, so a change reaches it with the next
jiayang deploy.env setsays so.
If a new version is still going live when you make a change, as a big site’s can be, the change is saved at once. It goes out with that version or straight after. env set says so:
Set REGION on acme/hello. Version 5 is still going live. The app gets the change once it's done. The live code sees the change within a minute or two. Builds bake in what they read, so if acme/hello is a site or its build uses it, run `jiayang deploy acme/hello` again.If it can’t go out then, the audit log has an app.env_not_applied for it.
A container app works differently. A change waits up to 20 seconds for a deploy that’s still going. If the deploy is still going after that, nothing is saved and you’re asked to try again.
Values read at build time are different. A static site’s build, and the values a front end exposes to the browser, are fixed when the build runs. Those are VITE_* for Vite, NEXT_PUBLIC_* for Next.js, PUBLIC_* for Astro and REACT_APP_* for Create React App. They change on the next jiayang deploy, and env set tells you so:
Set VITE_API_BASE on acme/web. Builds bake VITE_API_BASE in, so the app sees the change after the next `jiayang deploy acme/web`.Setting a variable to the value it already has changes nothing. env set says:
REGION already had that value on acme/hello. Nothing changed.It doesn’t redeploy either, unless the app may not have its values yet. That can happen after an env set, env remove, deploy or rollback stopped with an error. Then the next env set or env remove redeploys even when it changes nothing, and says so:
REGION already had that value on acme/py. The app may not have been running with these values, so we redeployed it with them. We're replacing the container with one that has the new values. That can take a minute or two. Until then, requests may reach the old one, wait, or get a 503 saying the app is starting.Before the first deploy
Section titled “Before the first deploy”jiayang deploy --create creates the app after its build has run, so that first build has no variables to read. For a site that needs a value baked in from the start, create the app, set the value, then deploy:
jiayang app create acme/webjiayang env set acme/web VITE_API_BASE=https://api.example.comjiayang deploy acme/webWith nothing deployed yet, env set says:
Set VITE_API_BASE on acme/web. Nothing is deployed to acme/web yet. Its first deploy will use these values.Rollback
Section titled “Rollback”Rolling back puts an earlier version’s code live with the variables as they are now. It doesn’t restore the values the app had when that version was made.
A name is letters, digits and underscores, doesn’t start with a digit, and is at most 64 characters. A value is at most 4,096 bytes with no control characters.
We set some names ourselves, so you can’t use them:
- anything starting with
JIAYANG_,CF_orCLOUDFLARE_; PORT,DB,APP,EGRESS,ASSETS;SSL_CERT_FILE,SSL_CERT_DIR,REQUESTS_CA_BUNDLE,NODE_EXTRA_CA_CERTS,CURL_CA_BUNDLE,GIT_SSL_CAINFO.
error: We set PORT ourselves. Pick another name.Credentials
Section titled “Credentials”A value that is certainly a credential is always refused. That covers a private key, a URL with a password in it, or a token with a recognisable prefix, such as a Stripe, GitHub, Slack, GitLab, npm, AWS, Google, Anthropic, OpenAI, Groq or Amazon Bedrock key.
jiayang env set acme/hello STRIPE_KEY=sk_live_...The error starts with STRIPE_KEY looks like a Stripe key. and points you to jiayang secret set, which keeps the key out of your app. See Secrets and outbound calls.
A variable that might be a credential is refused unless you confirm it isn’t one. This applies to a name containing SECRET, PASS, KEY, TOKEN, CREDENTIAL, AUTH, SALT, SIGNING or PRIVATE, a value shaped like a signed token, or a long random-looking value:
error: AUTH_REDIRECT_URL wasn't set because its name says it's a credential. Credentials belong in `jiayang secret set`. If this really is configuration, pass --allow-sensitive-name.jiayang env set acme/hello AUTH_REDIRECT_URL=https://acme.example/callback --allow-sensitive-name--allow-sensitive-name doesn’t override the refusal of a value that is certainly a credential.
A whsec_ value is a webhook signing secret. It’s refused under any name, even with --allow-sensitive-name:
error: STRIPE_WEBHOOK_SECRET looks like a webhook signing secret, and no flag sets one. Add a verifier to the path in the dashboard under Sharing, then Public paths, and paste it there. We check each delivery's signature, and the app checks our token with requireWebhook().With a verifier on the path, we check each delivery’s signature and your app doesn’t hold the secret. A variable refused as a possible credential that has WEBHOOK in its name, like GITHUB_WEBHOOK_SECRET, gets the same advice before the usual refusal. --allow-sensitive-name still sets it if your app has to hold it. See Webhooks and public paths.
Limits
Section titled “Limits”An app can have up to 25 variables on Free, 50 on Team and 100 on Business. One env set or env remove takes at most 100 names. A change that would go over the plan’s limit is refused, and nothing is changed:
error: That would give this app 26 environment variables, but the Free plan allows 25 per app. Remove some or upgrade. Team allows 50 environment variables per app. Upgrade the workspace at https://app.jiayang.cloud/w/acme/billingA container app gets its variables as one piece of JSON, names and values together, and that can be at most 5,000 bytes on either paid plan. REGION=eu and MODE=live go as {"REGION":"eu","MODE":"live"}, 29 bytes. The quotes and punctuation add about 6 bytes a variable to the names and values. A quote or a backslash in a value counts twice, as JSON escapes it. Plenty of short values fit, but two long ones may not:
error: A container app's environment variables, names and values together, must fit in 5,000 bytes of JSON. This would make them 8,015 bytes. Remove or shorten some.A Worker app gets each variable on its own, so this doesn’t apply to it.
Next steps
Section titled “Next steps”- Secrets and outbound calls for API keys and other credentials.
- Versions and rollback for what a rollback keeps.