Cloudflare Workers
One VARS_KEY secret. Ciphertexts bundled. Runtime decrypt via Web Crypto.
Setup
vars gen config.vars --platform serverlessThis embeds per-env ciphertexts in the generated module and emits an async getVars(env) that decrypts at runtime.
Worker code
import { getVars } from '#vars'
import { Hono } from 'hono'
const app = new Hono()
app.get('/', async (c) => {
const vars = await getVars(c.env)
return c.json({ app: vars.APP_NAME, db: vars.DATABASE_URL.unwrap() })
})
export default appgetVars is async (Web Crypto is async-only). The result is memoized per-isolate — subsequent calls in the same isolate return instantly.
Wrangler config
name = "my-worker"
main = "src/index.ts"
[env.production]
vars = { VARS_ENV = "prod" }
[env.preview]
vars = { VARS_ENV = "dev" }Then set the single runtime secret once per env:
wrangler secret put VARS_KEY --env production
# paste the output of: vars key export
wrangler secret put VARS_KEY --env previewThat's the complete deploy-time config. No per-secret wrangler secret put.
Local dev
{
"scripts": {
"dev": "vars run --env dev -- wrangler dev"
}
}vars run decrypts using your local VARS_KEY / PIN and injects into process.env; wrangler dev forwards env to the Worker's local env binding.
Example config.vars
env(dev, prod)
public APP_NAME = "worker-api"
API_SECRET : z.string().min(16) {
dev = "worker-dev-secret-val"
prod = "worker-prod-secret-val"
}
DATABASE_URL : z.string().url() {
dev = "postgres://localhost/worker_dev"
prod = "postgres://prod.db/worker_prod"
}Migrating from --platform cloudflare
--platform cloudflare was removed because it required pushing every secret to Cloudflare via wrangler secret put, defeating the "one key" value proposition.
wrangler secret put VARS_KEY --env production(once per env).- Add
[env.production] vars = { VARS_ENV = "prod" }towrangler.toml. vars gen config.vars --platform serverless.- Update call sites to
await getVars(env). - Remove any per-secret
wrangler secret putentries now managed byvars.