Env and secrets
How env vars get into your tool at runtime — declared, injected, and generated.
By the time your container boots, Vendo has assembled a complete environment from four sources: integrations, wizard inputs, deploy-context built-ins, and any secrets the platform generates on your behalf. This page covers the part of that assembly you control from vendo.yaml — the env: block — and the contract for what your code can expect.
The env: block
env:
required:
- DATABASE_URL
- JWT_SECRET
optional:
- LOG_LEVEL
- SENTRY_DSNenv.required lists env vars your tool must have to start. The deploy worker checks each one resolves to a non-empty value before launching your container. If any are missing, the deploy fails fast with a clear error pointing at the missing key.
env.optional is informational — the dashboard surfaces these as configurable, but the deploy proceeds whether or not they have values.
The env: block is a contract, not a definition. You don't put values here. The values come from:
- Integrations — declaring
integrations: [openrouter]causesOPENROUTER_API_KEYandOPENROUTER_BASE_URLto be injected. - Wizard inputs — declaring a
userInputs[]entry in your template manifest withkey: GREETINGcausesGREETINGto be injected with whatever the tenant typed. - Platform built-ins —
DATABASE_URL,REDIS_URL, R2 keys, etc., when your template requests those resources. - Generated secrets — declared in the template manifest's
secrets:block (see below).
Listing a var under env.required without one of those four supplying it will fail the deploy. The check is intentionally strict — silent missing env vars are the #1 cause of "deployed but not working" reports.
Var names must match ^[A-Z][A-Z0-9_]*$. Both required and optional reject duplicates within the array.
Generated secrets
Generated-secret declarations (secrets:, ${jwt_secret} placeholders, service env wiring) live in the template manifest (templates/<slug>/<version>.json in vendo-templates), not in vendo.yaml. The shape below is the template manifest's, validated by cloudflare/deploy-worker/src/schema/manifest.schema.json.
When your tool needs a stable random value — a JWT signing key, a session secret, an encryption key — declare it in the template manifest and reference it with a ${...} placeholder in the service env:
{
"secrets": {
"jwt_secret": { "type": "random_hex", "length": 32 }
},
"services": [
{
"name": "web",
"role": "web",
"env": { "JWT_SECRET": "${jwt_secret}" }
}
]
}Vendo generates the value once per deployment, persists it, and injects it on every restart. It never appears in logs, never leaves the platform, and rotates only if you explicitly rotate it from the deployment's settings page.
In vendo.yaml itself, you just list the env var name under env.required:
env:
required:
- JWT_SECRETYour code reads it like any other env var:
JWT_SECRET = os.environ["JWT_SECRET"]Platform built-ins
When your template requests a Postgres database, R2 bucket, or Redis instance, Vendo wires the connection details into env. The standard set:
| Variable | Source |
|---|---|
DATABASE_URL | The pooled Postgres URL for your deployment's database |
DATABASE_URL_UNPOOLED | The direct (non-pooled) Postgres URL — use for migrations |
REDIS_URL | Redis connection string, if your template includes Redis |
R2_BUCKET, R2_ENDPOINT, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY | R2 (S3-compatible) storage, if requested |
VENDO_DEPLOYMENT_ID | The UUID of this deployment — the SDK reads this automatically |
VENDO_DEPLOYMENT_SLUG | The deployment's slug (the user-facing piece of {slug}.vendo.run) |
VENDO_TENANT_ID | UUID of the tenant that owns this deployment |
VENDO_API_URL | Base URL for SDK API calls |
VENDO_API_KEY | The vendo_sk_* proxy key for this deployment |
The Vendo- and platform-prefixed vars are set on every deployment, regardless of vendo.yaml. You don't list them and you can't override them. They appear in templates as ${vendo_tenant_id}, ${vendo_deployment_id}, ${vendo_deployment_slug} placeholders, resolved by cloudflare/deploy-worker/src/secrets.ts.
What your tool should NOT do
- Don't read provider keys from env that the proxy serves. Even when
OPENROUTER_API_KEYis set, it's avendo_sk_*proxy key, not a raw OpenRouter key. Don't try to call OpenRouter directly with it — point your client atOPENROUTER_BASE_URLand let Vendo's proxy handle metering and gating. - Don't bake secrets into your Docker image. Images are reused across tenants; embedded secrets are a guaranteed leak. Read everything from env at runtime.
- Don't write secrets to disk outside a Railway volume. The container's writable layer is per-restart and not preserved across upgrades. Volumes (or Postgres / R2) are the only durable storage.
Inspecting env at runtime
Tenants can view the resolved env var set (with secret values masked) from the deployment's settings page. As a tool author, you can reproduce the same set locally with ./bin/repo dev --env for testing — that command pulls real per-tenant proxy URLs and a personal vendo_sk_* so your calls flow through prod metering against your own tenant.