Manage environment variables, locally or on a server. Everything that differs between environments is an environment variable, for the app and for PocketBase alike: locally they live in .env at the project root; on a server they live under /etc/vela, readable only by root, and never travel inside a release.
Syntax
$ vela env <command>list- Show which variables exist whereget- Print a public variableset- Set a variable, prompting for the valueunset- Remove a variableimport- Merge a dotenv file into the environment
Scopes
On a server, a target’s environment is three layers, lowest to highest. A variable in a higher layer wins:
$ vela env set VELASTACK_API_KEY -t all # every target, on every server the app is deployed to
$ vela env set WHATSAPP_MODE -t preview # every preview
$ vela env set STRIPE_SECRET_KEY -t production # one target-t all and -t preview write to every server the project’s targets are bound to, and restart every instance the layer reaches on each. A preview branch inherits both shared layers, so it is never empty on its first deploy; -t preview:<branch> addresses one preview on its own. -t local is the .env on this machine.
For env, a bare -t preview means the layer every preview shares; for vela deploy it means the branch checked out here. A preview API key is set once, a branch is deployed many times.
There is no default. Without -t, set, unset and import ask which environment at a terminal and refuse in CI; list shows every scope at once.
Secret unless said otherwise
A value on a server is secret by default: written, never read back, shown as ••••. Pass --public for one that should read back — vela env get prints it and vela env list shows it. PUBLIC_* keys are public without asking, since they are meant for the browser. Setting a key again with or without --public moves it; unset removes it from whichever side it was on.
Values are always prompted for, never taken as an argument, so none lands in shell history. A secret is typed without echo, a public value with, so the prompt itself says which kind you are setting.
The app reads what src/env.ts declares
SvelteKit exposes only the variables declared in src/env.ts. A server-side one is imported by name from $app/env/private, one declared public: true from $app/env/public; a variable that isn’t declared isn’t exposed at all, however it was set. The templates declare the variables they read, and an enable command that adds code reading one declares it too. A variable of your own needs an entry in the file’s defineEnvVars call before the app can see it:
MY_API_KEY: {
schema: (value) => value ?? '',
description: 'Key for the example API.'
},Vela’s declarations turn a missing value into '', so the app builds and starts with a blank .env.
PocketBase reads the same environment
With a backend, PocketBase takes its own settings from the same variables and shows them locked in the admin panel: APP_NAME (handed over from src/lib/site.ts), APP_URL (the deploy’s origin unless you say otherwise), PB_SMTP_HOST, PB_SMTP_PORT, PB_SMTP_USERNAME, PB_SMTP_PASSWORD, PB_SMTP_TLS, PB_SENDER_NAME, PB_SENDER_ADDRESS, PB_S3_*, PB_BACKUPS_S3_*, and the WhatsApp plugin’s WHATSAPP_* / VELASTACK_*. Setting PB_SMTP_HOST turns SMTP on; setting PB_S3_ENDPOINT moves uploads to that bucket. The mail and storage names are prefixed on purpose: an app’s own SMTP_HOST or S3_BUCKET must not switch PocketBase mail or storage on.
Options
Every subcommand takes the same target and connection options:
-t, --target <target>- Which environment:local,all,preview,preview:<branch>,production, or any bound target such asstaging--public- Onsetandimport: readable back--server <ssh>- Server a target runs on, recorded on first use; not taken byallorpreview, which go to the bound servers-i, --identity <file>- SSH private key to authenticate with--ssh-port <port>- SSH port--accept-host-keys- Trust an unknown host key on first connect (CI)
Deploys never write them
vela deploy does not upload or overwrite the environment on a server. It does read it: the build runs with the target’s resolved environment rather than your .env, so a prerendered page sees what the running app will. A key set only in .env is blanked for the build, and a $env/static import of one fails the deploy before anything is uploaded. A key the app imports from $env/static is baked at build time, so set says “redeploy to apply” for it rather than restarting.
On an instance’s first deploy, the keys .env.example documents that the target lacks are listed and, at a terminal, offered for setting. Nothing is required; the file documents, it does not gate.