Environments
One Chaski Worker per environment of your app, each with resources of its own, from one project.
Each environment of your app, such as production and staging, gets a Chaski Worker of its own,
with its own database, queue, dashboard and channel settings. One Chaski project deploys them all:
the top of its wrangler.jsonc is production, as Install sets it up, and every
other environment is a block under env.
Add an environment
In the Chaski project:
npx chaskijs env add staging --public-url https://staging.example.com/me/notificationsIt adds an env.staging block to wrangler.jsonc. Wrangler doesn’t carry bindings or vars from
the top of the file into an environment, so the block repeats each one, with that environment’s
names:
{
"name": "chaski",
// …production, as Install sets it up
"env": {
"staging": {
"vars": {
"CHASKI_ENVIRONMENT": "staging",
"CHASKI_PUBLIC_URL": "https://staging.example.com/me/notifications"
},
"d1_databases": [
{
"binding": "CHASKI_DB",
"database_name": "chaski-staging",
"database_id": "local",
"migrations_dir": "node_modules/chaskijs/migrations"
}
],
"queues": {
"producers": [{ "binding": "CHASKI_QUEUE", "queue": "chaski-staging-deliveries" }],
"consumers": [
{
"queue": "chaski-staging-deliveries",
"max_batch_size": 10,
"max_batch_timeout": 1,
"max_retries": 5,
"dead_letter_queue": "chaski-staging-deliveries-dlq"
}
]
},
"durable_objects": {
"bindings": [
{ "name": "CHASKI_FEED", "class_name": "ChaskiFeed" },
{ "name": "CHASKI_EVENTS", "class_name": "ChaskiEvents" }
]
},
"workflows": [
{
"name": "chaski-staging-scheduler",
"binding": "CHASKI_SCHEDULER",
"class_name": "ChaskiScheduler"
}
],
"send_email": [{ "name": "CHASKI_EMAIL" }]
}
}
}Wrangler names the Worker chaski-staging, after the top-level name and the environment, and
every resource follows: the database chaski-staging, the queue chaski-staging-deliveries. The
Workflow needs a name of its own in every environment, since its name is unique in your account.
Deploy it
npx chaskijs deploy --env stagingIt does for staging what it does for production: creates the resources staging is missing, writes
its database’s id, applies the migrations, and deploys chaski-staging with a CHASKI_SECRET of
its own. Each step runs the Wrangler command with --env staging. By hand, the first deploy of
chaski-staging takes its secret with npx wrangler deploy --env staging --secrets-file chaski-secret.env, as Install shows. A chaski-staging that already runs
without the secret gets it with npx wrangler secret put CHASKI_SECRET --env staging, after a
deploy: until the Worker exists, secret put creates an empty one under its name.
Then turn on Cloudflare Access for its URL and set up its channels in its own dashboard, as Install does for production. Settings never move between environments: staging can send from a test domain while production sends from your own.
Bind each app environment to its Chaski
Your app’s staging environment binds to chaski-staging, and its production to chaski:
{
"services": [{ "binding": "CHASKI", "service": "chaski", "entrypoint": "ChaskiEntrypoint" }],
"env": {
"staging": {
"services": [
{ "binding": "CHASKI", "service": "chaski-staging", "entrypoint": "ChaskiEntrypoint" }
]
}
}
}The code doesn’t change: it calls env.CHASKI in every environment, and each one reaches its own
Chaski.
Keep them in step
An upgrade goes to every environment the same way, staging first:
npx chaskijs upgrade
npx chaskijs deploy --env staging
npx chaskijs deployupgrade installs the latest chaskijs and brings wrangler.jsonc and the main module, in every
environment, up to what that version needs, such as a Durable Object migration for a class it
added. Each deploy then applies the new migrations before the new code. The dashboard’s header
names the environment from CHASKI_ENVIRONMENT, so nobody mistakes one for the other.
Updated Oct 30, 2026
MIT License · chaski.sh