Secrets and env vars
What gets injected at boot, naming conventions, and where the values live.
When your container boots on Vendo, its environment is already populated with everything it needs: a Postgres URL, a Vendo API key, integration tokens, your declared user-facing config. This page documents what's injected, who set it, and what you can change after the fact.
The four kinds of env vars
Every row in your deployment's env is tagged with a kind:
| Kind | Set by | When | User-editable |
|---|---|---|---|
system | Deploy worker | First deploy, generated once | No |
integration | Deploy worker / bindings API | First deploy, plus when connections change | No |
user | Wizard inputs / Settings tab | First deploy, edited any time | Yes |
wizard (legacy) | Wizard inputs | First deploy | Yes |
The Settings tab in the dashboard shows all four. You can edit user rows; system and integration are read-only badges (reveal still works, you can copy values, but you can't change them — they'd be overwritten on the next event that owns them).
system — Vendo-controlled
Set once at deploy time by the deploy workflow. Common keys:
| Variable | What |
|---|---|
DATABASE_URL | Pooled Postgres URL (Neon). Connect with this. |
DATABASE_URL_UNPOOLED | Direct Postgres URL. Use for migrations and long transactions. |
DATABASE_HOST, DATABASE_PORT, DATABASE_USER, DATABASE_PASSWORD, DATABASE_NAME | Components, for tools that need them split out. |
REDIS_URL | Redis URL, if your manifest requested one. |
R2_BUCKET, R2_ENDPOINT, R2_ACCESS_KEY, R2_SECRET_KEY | R2 bucket, if your manifest declared object storage. |
VENDO_API_KEY | App-scoped proxy key. Same value ${vendo_api_key} resolves to in the manifest. |
VENDO_TENANT_ID, VENDO_DEPLOYMENT_ID, VENDO_DEPLOYMENT_SLUG | Identifiers for runtime use. |
DOMAIN, SUBDOMAIN | Your public hostname. |
ADMIN_EMAIL, ADMIN_PASSWORD | Bootstrap admin creds, if your manifest declared bootstrap_admin. |
Generated secrets (any value your manifest declared as random_hex / random_base64 / uuid) are produced once during collect_secrets and stick for the life of the deployment.
integration — connection-derived
When a tenant binds an OAuth connection to your deployment, Vendo writes the relevant tokens as integration-kind env vars. The deploy workflow does this on first deploy; the POST /api/apps/[id]/bindings API does it on post-deploy bind.
What gets written depends on the provider, declared centrally in each integration's connectionEnvVars:
| Provider | Variables typically injected |
|---|---|
| OpenAI | OPENAI_API_KEY |
| Anthropic | ANTHROPIC_API_KEY |
| OpenRouter | OPENROUTER_API_KEY, OPENROUTER_BASE_URL |
| Telegram | TELEGRAM_BOT_TOKEN |
| Notion | NOTION_TOKEN |
For refresh-rotating profiles (most OAuth providers via Composio), Vendo deliberately does not inject the token as an env var — it would go stale within an hour. Your tool fetches via the SDK or credentials worker instead.
Tools that exclusively use refresh-rotating OAuth see no integration-kind rows, only system and user.
user — what your manifest's wizard asked for
Anything your template's wizardInputs[] collected from the user during the deploy wizard. Editable from the Settings tab post-deploy. After an edit, the dashboard surfaces a "Restart now" button — the new value isn't live in your container until the restart.
Reading env vars at runtime
Your code reads them with whatever your language's normal mechanism is:
- Node:
process.env.OPENAI_API_KEY - Python:
os.environ["OPENAI_API_KEY"] - Go:
os.Getenv("OPENAI_API_KEY")
No special SDK call required for env-injected values. Use the SDK when you need a refresh-rotating OAuth token (vendo.token("notion")) or when you want code that runs unchanged in OSS mode and Vendo mode.
Where the values actually live
You don't touch any of this; it's noted here so you can reason about behavior.
- Encrypted at rest in Postgres (
deployment_env_vars, AES-256-GCM). - Pushed to Railway as variables on the service at deploy + on every bind / restart.
- The Railway runtime decrypts and presents them as ordinary OS env to your container.
Adding a new user env after deploy is a write to Postgres + a Railway upsert + a service restart. The "Restart to apply" banner is exactly this.
Gotchas
- Restart, don't redeploy, for env changes. Editing a
userrow and restarting picks up the new value. Re-running the deploy wizard is unnecessary and slower. systemkeys are reserved. Trying toPATCH /envwith a key the deploy workflow owns returns400.- Idempotency. All env writes are upsert-with-insert-if-absent. A deploy retry won't clobber
useredits you've made post-deploy. - Renaming the deployment rewrites
DOMAIN,SUBDOMAIN, and a few connection-derived URLs, then restarts. Don't cache these values in memory beyond a single request.
Related
- Subdomains —
DOMAIN/SUBDOMAINsemantics. - Credentials worker — for tokens that don't live in env.