Skip to content

Cloudflare deploy

mx ships a Cloudflare deploy path as a conditional part of the folder contract: a make/cloudflare.mk module and an infra/cloudflare/ tree that exist only in projects that asked for them.

Terminal window
mx new my-app --infra cloudflare # at creation time
mx infra setup cloudflare # credentials, once per workstation
Terminal window
make cf-setup # interactive wizard: auth, account id, optional API token
make cf-init a=myapp type=worker # scaffold an app (worker | cron | container)
make cf-deploy a=myapp # deploy to production

Three worker types, chosen at cf-init:

TypeFor
workerstandard edge worker handling HTTP requests
cronscheduled worker, with KV for state and optional webhook notifications
containera container image behind a worker: port, sleep timeout, max instances, custom domain
TargetPurpose
make cf-setupInteractive setup wizard
make cf-varsShow the resolved credentials and the scope they came from
make cf-check-credentialsFail early, with both paths named, when no account id resolves
make cf-login / make cf-whoamiAuthenticate / show auth status
make cf-status / make cf-listAll apps’ status / list configured apps
make cf-init a=<app> type=<type>Initialize an app
make cf-install a=<app>Install worker dependencies
make cf-dev a=<app>Run the worker locally
make cf-build a=<app>Build the container image
make cf-push a=<app>Push to the Cloudflare registry
make cf-publish a=<app>Build and push
make cf-images a=<app>List registry images
make cf-sync-image a=<app>Sync wrangler.toml to a new image tag
make cf-deploy a=<app>Deploy to production
make cf-deploy-preview a=<app>Deploy to preview
make cf-deploy-allDeploy every configured app
make cf-logs a=<app> / cf-logs-previewTail production / preview logs
make cf-restart a=<app>Restart the container
make cf-container-status a=<app>Check container status

Per-app configuration lives in that app’s wrangler.toml. Credentials come from mx infra or from make cf-setup, and both are read: the environment first, then the project’s infra/cloudflare/.env.cloudflare, then the global ~/.mech-crate/config/infra/cloudflare.env. One mx infra setup cloudflare per workstation is enough, and make cf-setup is the override for a project that deploys to a different account. make cf-vars prints which scope answered; make cf-check-credentials fails loudly, naming both paths, when none did.

Worker type internals, directory layout, per-app wrangler.toml configuration, versioning, CI/CD wiring and troubleshooting:

→ Cloudflare Infrastructure

And a researched, as-implemented account of the same flow, including a drift inventory of the places the documentation and the code disagree. Worth reading before you debug a credential problem:

→ mx Cloudflare Deploy: as-implemented flow, credentials, and known traps

mechcrate.dev is built from site/ in the mech-crate repository, a nested mx project with the Astro app at site/apps/site/. The intended deploy is the astro recipe’s Cloudflare path: a GitHub Actions workflow that runs astro build on pushes to main touching site/** or docs/** and publishes the static bundle with wrangler, plus a preview deploy on pull requests. The site job is independent of the Rust gates. It never blocks ci.yml and is never blocked by it.

That workflow is now .github/workflows/site.yml. site/ was scaffolded without --infra cloudflare, so it has no make/cloudflare.mk. The workflow calls wrangler directly rather than through the cf-* targets.

Two things about the dev container are specific to this site, and both are commented where they live:

  • No database or cache. The astro recipe’s compose includes db.yml and redis.yml; a static documentation build needs neither, so docker/compose/site.yml drops them.
  • The corpus lives outside the app. The techniques corpus is read from the repository’s docs/development/, which is above apps/site and therefore outside the container’s source mount. docker/compose/site.dev.yml bind-mounts docs/ read-only at /repo/docs and sets MECHCRATE_REPO_ROOT, so make dev renders the same 110 pages CI builds. Without it the site still comes up. It just silently loses all 67 corpus pages.

The production deploy does not use this container at all: CI runs astro build and ships the static dist/ to Cloudflare.