Environment variables
Environment variables are an app’s plain configuration: an API’s base URL, a region, a feature switch. They belong to the app rather than to one version. They’re stored as plain text that anyone who can deploy the app can read, so credentials go in secrets instead.
Set, list and remove
Section titled “Set, list and remove”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, as it reaches every location. A build bakes in what it reads, so if acme/hello is a site or its build uses them, `jiayang deploy acme/hello` again.jiayang env list acme/helloAPI_BASE=https://api.example.comREGION=eujiayang env unset acme/hello REGIONRemoved REGION from acme/hello. The live code sees the change within a minute, as it reaches every location. A build bakes in what it reads, so if acme/hello is a site or its build uses it, `jiayang deploy acme/hello` again.Everything after the first = is the value, so a value can hold = itself. env set and env unset print names only, never values, so they’re safe in a CI log. env list prints values: that’s what it’s for.
The app’s Environment tab in the dashboard does the same.
Setting, changing and removing variables takes an editor or owner of the app, or a workspace admin or owner. So does listing them. Each change goes in the audit log with the names it touched, not 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 jiayang deploy runs gets the app’s variables in its environment too.
When a change arrives
Section titled “When a change arrives”Setting or removing a variable puts the live version out again with the new values. You don’t need to deploy for code that reads them while it runs:
- A Worker’s code sees the change once the new configuration reaches every location, which can take up to a minute. Until then, some requests still get the old values.
- A container starts again with the new values. The change rolls it out again with the same image, and if that hasn’t replaced it by the next request, that request stops it first. Until the new container is up, which can take a minute, requests may still reach the old one, wait, or get a 503 saying the app is starting.
What a build read is another matter. A static site’s build, and the values a front end exposes to the browser, are fixed when the build runs: VITE_* for Vite, NEXT_PUBLIC_* for Next.js, PUBLIC_* for Astro and REACT_APP_* for Create React App. Those change on the next jiayang deploy, and env set says so:
Set VITE_API_BASE on acme/web. A build bakes VITE_API_BASE in, so the app sees the change after the next `jiayang deploy acme/web`.A value set to what it already is changes nothing and puts nothing out again, and env set says that instead:
REGION already had that value on acme/hello. Nothing changed.Before the first deploy
Section titled “Before the first deploy”jiayang deploy --create makes the app after its build has run, so that first build has no variables to read. For a site that needs one baked in from the start, create the app first, 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 uses what's set.Rollback keeps the current values
Section titled “Rollback keeps the current values”Variables belong to the app. Rolling back puts an earlier version’s code live with the variables as they are now, not as they were 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.
The platform sets some names itself, so you can’t:
- 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: PORT is set by the platform. Pick another name.Credentials are refused
Section titled “Credentials are refused”A value that is certainly a credential is refused, with no way around it: a private key, a URL with a password in it, or a token whose prefix gives it away, such as a Stripe, GitHub, Slack, GitLab, npm, AWS, Google, Anthropic or OpenAI 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 entirely. See Secrets and outbound calls.
A value that only reads like a credential is refused until you say it isn’t one. That’s 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 never lets through a value that is certainly a credential.
A whsec_ value is a webhook’s signing secret, and it’s refused whatever it’s called, 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 (Sharing, Public paths) with it instead. The platform then checks each delivery's signature, and the app checks the platform's token with requireWebhook().With a verifier on the path, the platform checks each delivery’s signature and your app never holds the secret. A variable refused for reading like a credential, with WEBHOOK in its name, like GITHUB_WEBHOOK_SECRET, gets the same advice before the usual refusal, and --allow-sensitive-name still sets it if your app really has to hold it. See Public paths and webhooks.
Limits
Section titled “Limits”An app can have up to 25 variables on Free and 50 on Team and Business. One env set or env unset takes at most 50 names. Over the plan’s limit, nothing is changed:
error: That would give this app 26 environment variables, more than the Free plan allows per app (25). Unset some, or upgrade: Team allows 50 environment variables per app. Upgrade the workspace at https://app.jiayang.cloud/w/acme/billing- Secrets and outbound calls for API keys and other credentials.
- Versions and rollback for what a rollback keeps.