2-11 / Series
2-11 № 11 · 2026

Two homes,
one procedure.

Ship the baked HTML to either home, by the same procedure

In 2-10: Build the Web — Back to HTML, CSS, and JavaScript we settled the shape: content as text, the frame in HTML and CSS, and Python baking the two together. Here we ship that baked html/ outward.

There are two homes for it: your own machine from 2-02, or Cloudflare Pages. The procedure is the same either way, so you can start on one and move to the other later.

Decide the shape of publishing first

This chapter settles six things. When you hand this chapter to an AI, these decisions are what you are handing over — not code.

Choose between two homes

[cols="1,2,2"]

Your own machine (2-02 + Caddy) Cloudflare Pages
The machine
Yours, the one box from 2-02
Borrowed; no machine to look after
Certificate
Caddy takes it from Let's Encrypt and renews it
Cloudflare adds it automatically
Delivery
As wide as your own line
From a global CDN
Moving parts
Straight to FastAPI on the same machine
To the API behind the gate on your own machine, from outside
Suits
Internal use, publishing at a size you can see, and keeping the IoT intake on the same box
Heavy traffic, and not wanting to think about the line or the machine

If you are unsure, start on your own machine. The box from 2-02 is already standing, and all you add is a few lines of Caddy. If traffic grows and the line starts to matter, put the same html/ on Cloudflare Pages and you have moved the same day.

Put it on your own machine

Stand Caddy in front of the Debian box from 2-02. Caddy is in Debian 13's apt and runs under systemd (2-02). It takes ports 80 and 443, fetches the certificate, and renews it. The configuration is this much.

example.com {
    root * /srv/html
    file_server
    handle /api/* {
        reverse_proxy localhost:8000
    }
}

The public web (static files) and the moving parts (FastAPI) now sit under one domain. Nothing crosses to a second origin, so the browser needs no permission settings.

Three things to decide.

Put it on Cloudflare Pages

To publish while holding no machine, use Cloudflare Pages. Upload the files and they are served from a global CDN, with HTTPS added automatically.

Leave delivery and defense to Cloudflare, and keep the source and the build in your own hands. The published HTML holds no secrets, so this part is safe to hand over.

Uploading needs no npm and no Node — one script hitting Cloudflare's API is enough. That script is published as aiseed-dev/cf-publish (on PyPI, uv tool install cf-publish): one line, and only the files that changed go up.

Separate build, verify, and deploy

Whichever home you chose, the procedure is the same. Do not fold it into one command.

uv run python tools/build.py                     # 1. bake
uv run python -m http.server --directory html 8000   # 2. look at it locally
rsync -a --delete html/ user@example.com:/srv/html/   # 3a. to your own machine
cf-publish html --project <name>                 # 3b. to Cloudflare Pages

Do not use auto-rebuild, and do not use a command that combines "build and deploy." The point is to *keep the HTML you verified identical to the HTML that goes live*. Where there are moving parts, the "verify" step runs the four pre-launch checks of 2-12. Cloudflare Pages has a preview branch ahead of production, so you can look before pointing the production URL at it. On your own machine, adding a second hostname on the same box does the same thing.

Connect the domain

Leave mail (MX, SPF) untouched. You are changing where the web is served, and nothing else. If an old server is running, keep it running while you verify the new side, and point the name only once you have.

Wire the moving parts with FastAPI

A contact form, a booking intake, a job search — that is about the extent of what moves. Gather it on one FastAPI.

People from outside confirm their mail address first, too. A reply needs an address, and a confirmed address brings neither typos nor fake submissions. No password and no account creation — one number to type. With that, every moving part sits behind the gate.

How you wire it depends on the home. On your own machine, Caddy sends /api/* to FastAPI on the same box. On Cloudflare Pages, the public side reaches the FastAPI on your own machine from outside. Either way, only people whose identity the gate has confirmed get through.

Separate the public side from behind the gate

The public site can live in either home. It is static and holds no secrets. The internal tools stood up in this part — gate, documents, code, mail, meetings — are handled separately. They carry secrets and raw data, so they stay behind the gate.

A borrowed window can be swapped at any time. The public site's substance is a pile of static files and the sources in your own hands. Send the same html/ to another home tomorrow, and the move is complete. The window may be borrowed, but the exit is always open.

How to check you are done

This chapter is done when these five hold.

  1. The published site opens on your own domain and the browser shows the lock mark
  2. The page you looked at on port 8000 locally and the production page look the same
  3. Before pointing production, the same page opens and can be checked at another URL
  4. Sending the contact form after typing the number that arrived by mail produces a notification in the 2-08 mail and a row in the 2-03 DB
  5. Mail still arrives as before, because MX was never touched
curl -sI https://example.com | head -1      # the published page answers
curl -sI https://example.com/api/health     # the moving parts answer under the same name

What the human holds

Values the human supplies

Actions the AI states before performing

Versions checked, and when

Summary

Ship the baked HTML to one of two homes.

Next, we expose the core systems' logic as an API (FastAPI) so every app can use it.


Related articles