Deploy with Wrangler
Deploy from a checkout with the deploy script, and keep deployment-specific settings in a profile.
Use this path when you want to deploy from your own machine or from your own
CI, or when your deployment needs settings that the one-click form cannot
express: a custom domain, a fixed database, or the kms vault mode.
Requirements
- Node.js 22.19 or newer and the pnpm version declared in
package.json. - A Cloudflare account.
pnpm installbrings Wrangler 4 with it; runpnpm exec wrangler loginonce so it can act on your account. openssl, to generate the vault key.
Deploy
From a clean checkout:
pnpm install
cp .dev.vars.example .dev.vars
openssl rand -base64 32 # paste the output into SECRET_VAULT_LOCAL_KEK_V1 in .dev.vars.dev.vars is gitignored. Its SECRET_VAULT_LOCAL_KEK_V1 encrypts provider
credentials; back it up because losing it makes stored credentials unreadable.
Then generate the initialization values, upload the secrets and deploy:
pnpm run secrets:setup-local
pnpm run secrets:upload
pnpm run deployRun pnpm run secrets:setup-local to generate the immutable public
DEPLOYMENT_ID and the internal signing secrets in ignored .dev.vars.
For a profile, put its own ID in .dev.vars.<profile> or set it in that
profile's plain vars. Preserve the ID on every deployment.
The upload helper excludes it from secrets; deployment supplies a plain binding.
Do not reuse a local development identity for a separate production deployment.
The deploy script does the following, in order:
- Builds the console.
- Checks the required vault secrets. If one is missing and
.dev.varsis present, it uploads only missing names, so the explicitsecrets:uploadabove is a convenience rather than a requirement. - Creates
JWT_SECRETandBETTER_AUTH_SECRETif they do not exist yet. Existing secrets are never overwritten. - Applies the database migrations. On the first run the database does not exist yet, so the script deploys once to provision it and then applies the migrations.
- Deploys the Worker with the console bundled as static assets.
Run it again at any time. It is safe to repeat and it is also how you update; see Updating.
Verify and sign in
curl https://YOUR-WORKER.workers.dev/v1/healthzExpect "vault": "ok". An empty deployment lets its first person register in
the console. To initialize with the CLI instead, create and claim its account privately:
node scripts/bootstrap-account.mjs https://YOUR-WORKER.workers.dev
agw deployment connect --url https://YOUR-WORKER.workers.dev --key-stdin < .agw-bootstrap/management-key
agw account claimInstall @maxceem/agw first if necessary. The helper saves recovery proofs
before requesting an account and stores its credential privately. Keep its state
when retrying a lost response. Complete the browser claim using the separate
terminal code and choose whether the CLI retains access. Once CLI initialization
creates the account, ordinary registration cannot take it; its first person must
use this protected claim.
Initialize straight after deploying
A deployment with no account yet belongs to whoever takes it first, whether by registering in the console or by running the helper above. Do that yourself as soon as the health check passes. A deployment nobody has taken holds no provider credentials, so nothing of yours is exposed; if someone else does get there first, delete its D1 database, create it again, apply the migrations and deploy once more to start from empty.
Deployment profiles
The tracked wrangler.jsonc is the complete configuration for a plain
installation. It deliberately has no custom domain and no account-specific
database ID. Those belong in a deployment profile.
A profile is a gitignored wrangler.<profile>.overlay.jsonc in the repository
root that holds only the keys your deployment changes. It is not a complete
Wrangler configuration. Every command merges it over wrangler.jsonc first, so
shared sections such as the resource migrations list are never copied and
cannot drift. The merge rules are small:
- Objects merge recursively.
- An array in the overlay replaces the base array.
nullremoves a key.
{
"workers_dev": false,
"routes": [{ "pattern": "console.example.com", "custom_domain": true }],
"vars": {
"ALLOW_ADDITIONAL_REGISTRATIONS": "false",
"SECRET_VAULT_MODE": "kms",
"SECRET_VAULT_LOCAL_KEK_CURRENT_VERSION": null
},
"d1_databases": [
{
"binding": "DB",
"database_name": "app-ai-gateway-prod",
"database_id": "00000000-0000-0000-0000-000000000000",
"migrations_dir": "migrations"
}
]
}This example switches the vault to kms mode, which is why it removes
SECRET_VAULT_LOCAL_KEK_CURRENT_VERSION: a local variable present in kms
mode is a configuration error. Keep local mode and drop those two vars
lines if you only want the domain and the database. See
Configuration for both modes.
Every script accepts the profile by name:
pnpm run deploy --profile prod
pnpm run deploy:dry-run --profile prod
pnpm run db:migrate --profile prodThe merged result is written to wrangler.<profile>.generated.jsonc, also
gitignored, and handed to Wrangler with --config. Do not edit it; change the
overlay instead.
A profile reads its secrets from .dev.vars.<profile> instead of .dev.vars.
On a profile's first deployment the script uploads missing secret values from that file when it exists,
so put the secrets the profile's vault mode requires there: in kms mode that
is SECRET_VAULT_KMS_URL and SECRET_VAULT_KMS_TOKEN, in local mode it is
SECRET_VAULT_LOCAL_KEK_V1.
Keep overlays out of version control
Overlays are the only place account identifiers, domains, and service
bindings should live. They are gitignored by pattern; never force-add one and
never copy its contents into wrangler.jsonc.
Next
Sign in to the console and follow the Quickstart: add a provider key, create an app, and send the first request. When you are ready for your own hostname, continue with Custom domain and rate limiting.