Deployment
Stand up a protected Worker without losing local mode
Hosted Fleet uses a custom-domain Worker, Cloudflare Access, D1, and encrypted Worker secrets. Generated deployment and operator files stay ignored; the committed defaults are safe, read-only, and unscheduled.
Prerequisites
- Node.js 22 or newer and npm
- A Cloudflare account with an active zone for the Fleet hostname
- Permission to create Workers, a D1 database, a custom domain, and a Cloudflare Access application
- An account API token scoped to the resources Fleet should inspect and, if enabled, change; see the least-privilege starting point
- A configured Cloudflare Zero Trust team domain and identity provider
Begin read-only
The configuration generator defaults to FLEET_READ_ONLY=true. Inspect the deployed inventory and Access boundary before regenerating with --write.
Hosted path
Provision the Worker
1
Install the pinned toolchain
The lockfile pins Wrangler, the Access JWT library, and the browser test stack. Keep the source checkout for deployment, but install the tagged GitHub package for a durable operator command that does not link back to the checkout.
npm install --global "github:j-256/cloudflare-fleet#v0.1.0"
npm ci
cloudflare-fleet --help
2
Create the D1 database
Record the database identifier returned by Wrangler. The generated configuration binds it as FLEET_DB.
npx wrangler d1 create cloudflare-fleet
export CLOUDFLARE_FLEET_D1_DATABASE_ID="the-returned-database-id"
3
Create the Access application
In Cloudflare Zero Trust, create a self-hosted application for the exact Fleet hostname and attach an Allow policy for the intended operators. Copy the Application Audience tag from the application settings and use the full HTTPS team domain as the issuer. The Worker validates both values on every non-loopback request.
Cloudflare documents the flow in Publish a self-hosted application and explains the audience and issuer in Validate JWTs.
4
Generate the ignored Wrangler configuration
Use an exact custom-domain hostname with no existing CNAME. Cloudflare creates the DNS record and certificate when Wrangler deploys the custom domain.
export CLOUDFLARE_ACCOUNT_ID="your-account-id"
export CLOUDFLARE_ACCESS_AUD="your-access-application-audience"
export CLOUDFLARE_ACCESS_TEAM_DOMAIN="https://your-team.cloudflareaccess.com"
cloudflare-fleet hosted configure \
--account-id "$CLOUDFLARE_ACCOUNT_ID" \
--database-id "$CLOUDFLARE_FLEET_D1_DATABASE_ID" \
--hostname fleet.example.com \
--access-aud "$CLOUDFLARE_ACCESS_AUD" \
--access-team-domain "$CLOUDFLARE_ACCESS_TEAM_DOMAIN"
The command writes mode-restricted wrangler.jsonc. That file contains account and deployment identifiers, so Git ignores it. wrangler.example.jsonc documents the portable shape without live values.
5
Apply storage and deploy the secret with the code
Wrangler accepts an ignored dotenv file through --secrets-file, allowing the first code version and encrypted secret to deploy together. Delete the local secret file after deployment if the shell environment is your preferred source of truth.
npm run db:migrate:remote
umask 077
printf 'CLOUDFLARE_API_TOKEN="%s"\n' "$CLOUDFLARE_API_TOKEN" > .dev.vars.production
npm run deploy -- --secrets-file .dev.vars.production
The custom domain is the Worker origin, so Cloudflare manages its DNS record and certificate. The Worker serves no workers.dev endpoint and disables preview URLs.
6
Verify Access and inventory
Open the configured hostname in a normal browser. Access should authenticate first, then Fleet should report a read-only session and begin loading the account. Test an unauthenticated private window as well: the Worker itself must reject a request that arrives without a valid Access assertion.
7
Opt into reviewed writes
After reviewing the account boundary, Access policy, proxy allowlist, and read-only inventory, regenerate the same ignored file with --write and deploy again.
cloudflare-fleet hosted configure \
--account-id "$CLOUDFLARE_ACCOUNT_ID" \
--database-id "$CLOUDFLARE_FLEET_D1_DATABASE_ID" \
--hostname fleet.example.com \
--access-aud "$CLOUDFLARE_ACCESS_AUD" \
--access-team-domain "$CLOUDFLARE_ACCESS_TEAM_DOMAIN" \
--write
npm run deploy
Operator policy exceptions
Fleet ships with no policy exceptions. If one zone intentionally uses an exact Email DNS record that differs from the derived fleet baseline, copy fleet-policy.example.json to the per-user Fleet configuration directory and edit the relevant fields. Local launches load it directly; cloudflare-fleet hosted configure validates and embeds the same document into the ignored Wrangler variables.
umask 077
fleet_config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/cloudflare-fleet"
mkdir -p "$fleet_config_dir"
cp fleet-policy.example.json "$fleet_config_dir/fleet-policy.json"
cloudflare-fleet hosted configure ...
An Email DNS exception does not hide the matrix difference. It removes only the exact allowed variant from actionable policy drift.
Import existing local intent and activity
When moving an established local fleet to hosted mode, apply the migration first and import the ignored account-scoped state file once.
cloudflare-fleet hosted import-state
The importer reads the D1 identifier from the ignored Wrangler configuration and refuses to replace populated hosted state. Use --force only after reviewing both local and remote data.
Keep the local interfaces
Hosted deployment does not replace local mode. On macOS, provide the account token and identifier in the shell and launch the ordinary browser workflow. It shares the same UI, policy, intent schema, write planner, activity model, and verification code.
export CLOUDFLARE_API_TOKEN="your-account-token"
export CLOUDFLARE_ACCOUNT_ID="your-account-id"
cloudflare-fleet dashboard
Use cloudflare-fleet dashboard --write for the full local workflow. State defaults to $XDG_STATE_HOME/cloudflare-fleet/state.json, falling back to ~/.local/state/cloudflare-fleet/state.json; policy uses the corresponding per-user configuration directory. Select another profile with --state-file or --policy-file. The checkout-local ./launch.sh wrapper accepts the same dashboard options.
The local fleet CLI and stdio MCP server inherit the same credentials and use the same durable state profile:
cloudflare-fleet alignment list --format json
cloudflare-fleet mcp
An MCP client should use cloudflare-fleet as its command and ["mcp"] as its arguments. Keep the token in the inherited process environment, not a tracked or shared client configuration. The server opens no listener and exposes only named diagnostic, audit, intent, alignment, bounded change, activity, and guarded undo tools. The MCP setup guide includes copyable Codex and Claude Code configuration.
Keep the repository safe to publish
Operator state, deployment configuration, live screenshots, and browser traces stay outside version control. Validate the public source tree before proposing a change.
npm run check:publication
The publication check rejects private operator filenames, machine-local paths, unexpected symbolic links, malformed screenshots, and broken documentation links.
Before writes
Review the security model.
Understand the Access, Worker, proxy, persistence, and browser boundaries before enabling mutations.
Open security