For weeks I was deploying this site by hand.

Edit a post. Open a terminal. cd blog-astro. npm run deploy. Wait for wrangler. Watch the URL update. Close the terminal.

It worked. It was also the first thing I dropped any time a deploy happened to break, because the laptop I last deployed from was the only thing that knew the site was supposed to be different now.

The actual problem

This site uses Cloudflare Workers, with Astro built into a dist/ folder and uploaded via wrangler deploy. Wrangler does not need the repo to be public, or pushed, or even committed. It reads the local source and ships whatever is there.

Convenient for hot fixes. Bad for everything else.

The split between “what is on GitHub” and “what is live on the domain” is invisible until you forget which side is ahead. I forgot more than once. A friend opened the live site, told me it was missing a feature I had merged days earlier, and the only fix was to walk back to the same laptop and run the same command again.

The fix is one dashboard panel

Cloudflare Workers Builds is the Worker-side answer to what Pages has had for a while. It connects a GitHub repo to a Worker and runs the build on Cloudflare’s infrastructure on every push.

The setup is one panel in the Cloudflare dashboard. The fields that mattered were:

  • Repository and branch. Point at the repo, pin to main.
  • Root directory. Set this to blog-astro. The repo has no root-level package.json, so without this Builds runs npm install in the wrong folder and dies.
  • Build command. npm install && npm run build.
  • Deploy command. npx wrangler deploy.
  • NODE_VERSION build variable. The repo uses Volta locally to pin Node, but Volta is local-only and Cloudflare’s build VM ignores it. Set the version explicitly so the deploy environment matches the development one.

That is the whole list. No GitHub Actions workflow, no API token in the repo, no secrets to rotate. Cloudflare creates a scoped token for itself and does the work.

What this changed in practice

The git workflow is now the deploy workflow. Open a PR. Review it. Merge. The Builds tab shows the run starting within a few seconds, and the live site updates a minute later. Every deploy has a build log, a commit SHA, and a person who pressed the merge button.

I noticed two things on the first run. The cold-start build was slower than the steady-state ones, because npm had nothing to cache yet. And the first build silently used a Node version one minor behind the local Volta pin until I set NODE_VERSION explicitly. Easy to miss, since both versions could install dependencies fine.

What I would tell past me

Set up the auto-deploy on day one, not after the third “wait, that change isn’t live?” message. Manual deploy is faster to set up exactly once, and slower forever after. The CI you skip writing is not free. It just gets paid in small confused minutes whenever the laptop and the live site disagree.

This post was the test push. If you are reading it and the timestamp matches the merge, the pipeline works.