The map
Two paths matter. The visitor path is what happens when you load this page. The operator path is how changes get made. Neither path ever touches an open port on a server.
You never see the server
DNS points at Cloudflare. Cloudflare terminates HTTPS and forwards the request down a tunnel the server opened itself.
One door for admin work
A small always-on box holds the SSH key and API tokens. It is the only machine allowed to change anything.
Containers, not root
Each site is a rootless container run by an unprivileged user and supervised by systemd.
Cloudflare does the heavy lifting
Registrar, DNS, certificates, caching, DDoS protection and the identity check for SSH, all on one account.
01Buying the domain with code
Cloudflare's Registrar API (a beta since April 2026) lets a script search, check and register domains. That turns "go buy a domain" into three HTTP calls.
- Search every extension for the cheapest priceThe API supports a little over 400 extensions. A script price-checked one name against all of them, 20 at a time. The cheapest first year was $4.99, but those renew at $25 to $42.
.fyiwas $5.20 for the first year and $5.20 every year after. - Check again right before buyingSearch results are cached. The check call asks the registry directly, and the script refuses to buy if the price or availability has changed.
- A human says yesRegistrations can't be refunded, so the operator approved the exact domain and price first. Automation does the work, and a person signs off on spending money.
- RegisterOne POST. It finished in about ten seconds, with WHOIS privacy on by default and the domain locked against transfers.
# 1. availability + price (read-only, free)
curl -X POST $API/accounts/$ACCOUNT/registrar/domain-check \
-H "Authorization: Bearer $REGISTRAR_TOKEN" \
-d '{"domains":["jamalbuilds.fyi"]}'
→ {"registrable":true,"tier":"standard",
"pricing":{"registration_cost":"5.20","renewal_cost":"5.20"}}
# 2. buy it (charges the account's card, non-refundable)
curl -X POST $API/accounts/$ACCOUNT/registrar/registrations \
-H "Authorization: Bearer $REGISTRAR_TOKEN" \
-d '{"domain_name":"jamalbuilds.fyi","years":1,"auto_renew":false}'
→ {"state":"succeeded","completed":true}
Because the domain was bought at Cloudflare, its DNS zone already existed on the account, with Cloudflare's nameservers set, so there was no nameserver step. The one wait was the .fyi registry itself, which publishes a new domain's delegation a little after the sale. Until it does, every resolver on earth answers "no such domain".
02The bastion host
A bastion is the one machine trusted to administer the others. Here it is a $5/month VM that runs Claude Code inside tmux, so the operator can drive it from a phone.
No open ports
Port 22 is closed at the firewall. The box reaches the internet only by dialing out, including a Cloudflare Tunnel for its own SSH.
Identity before network
To SSH in, Cloudflare Access first checks an email login or a service token. Only then does the SSH key get a chance.
Survives reboots
A systemd user service with lingering enabled restarts the Claude Code session at boot. A deliberate reboot test came back with no hands on the keyboard.
Holds the secrets
API tokens sit in one chmod 600 file. Each token is scoped: one for DNS/Tunnel/Access, a separate one for the registrar.
# ~/.ssh/config: SSH rides through Cloudflare, never a public IP
Host bastion
HostName ssh.<bastion-domain>
User jamal
ProxyCommand cloudflared access ssh --hostname %h
Least privilege on the web server
The bastion logs into the web server as an ordinary user. The only root actions it can take are one helper script that edits the tunnel config, through a narrow sudoers rule. That helper:
- validates the new config before using it,
- backs up the old one,
- restarts the tunnel, and rolls back automatically if it isn't healthy within 20 seconds,
- refuses to touch routes that belong to other apps on the same server.
03The web server
The server already runs other sites. Each one is a container that only listens on 127.0.0.1, so it can't be reached from outside even if a firewall rule slipped.
# ~/.config/systemd/user/jamalbuilds.service (abridged)
ExecStart=/usr/bin/podman run --rm --name jamalbuilds-web \
-p 127.0.0.1:9380:80 \
-v %h/jamalbuilds/site:/usr/share/nginx/html:ro,z \
docker.io/library/nginx:1.27-alpine
Restart=always
| Choice | Why it matters |
|---|---|
| Rootless Podman | The container runs as an unprivileged user. A break-out lands in a normal account, not root. |
| systemd user units | Start at boot and restart on crash, with no root needed to install or manage them. |
| Bound to 127.0.0.1 | Only the local tunnel daemon can reach the site. |
Read-only mount, :z label | nginx can't modify the files. SELinux stays in enforcing mode and the label lets the container read them. |
Deploy = rsync + restart | The site is plain files in a folder. A deploy script syncs them and restarts the unit. |
04Tunnel and DNS
A Cloudflare Tunnel is a long-lived outbound connection from the server to Cloudflare. Requests for a hostname travel back down that connection. Adding a site means two changes: one route in the tunnel config, and one DNS record.
# tunnel ingress rules (first match wins)
ingress:
...
- hostname: jamalbuilds.fyi
service: http://127.0.0.1:9380
- hostname: www.jamalbuilds.fyi
service: http://127.0.0.1:9380
- service: http_status:404 # everything else
# applied through the guarded helper
sudo jbops-tunnel show > c.yml # edit c.yml
sudo jbops-tunnel check < c.yml # validate
sudo jbops-tunnel apply < c.yml # back up, restart, auto-rollback
Then DNS. Both names become proxied CNAMEs pointing at the tunnel. "Proxied" means the public answer is a Cloudflare address, so the server's real IP is never published.
| Name | Type | Target | Proxy |
|---|---|---|---|
jamalbuilds.fyi | CNAME | <tunnel-id>.cfargotunnel.com | on |
www | CNAME | <tunnel-id>.cfargotunnel.com | on |
curl -X POST $API/zones/$ZONE/dns_records \
-H "Authorization: Bearer $DNS_TOKEN" \
-d '{"type":"CNAME","name":"jamalbuilds.fyi",
"content":"<tunnel-id>.cfargotunnel.com","proxied":true}'
A CNAME at the zone apex normally isn't allowed. Cloudflare "flattens" it into A/AAAA answers on the fly.
05Where "serverless" comes in
There is a server here, but nobody on the internet ever talks to it. From the visitor's side, everything public is a Cloudflare service:
HTTPS for free
Cloudflare issues and renews the certificate at the edge. The origin never handles TLS.
Origin hidden
No A record points at the server, and the server has no open web ports. The only way in is the tunnel.
Edge network
Requests land at the nearest Cloudflare location, which can cache static files and absorb floods before they reach the VM.
Everything is an API
Registrar, DNS, tunnels and Access are all API calls. That is what let the whole build run from a terminal session.
How long it took
Measured on 12 November 2026, in UTC. The clock starts when the operator approved the purchase and stops when this page loaded over HTTPS.
| Time | + | Step |
|---|---|---|
08:35:38 | 0:00 | Operator approves. Price re-checked ($5.20), registration call sent. |
08:35:48 | 0:10 | jamalbuilds.fyi registered. DNS zone already live on Cloudflare. |
08:38 | ~2:30 | Page written, rootless nginx container started as a systemd user service. |
08:38:23 | 2:45 | Tunnel routes applied through the guarded helper (validate, back up, restart). |
08:39 | ~3:30 | Two proxied CNAMEs created by API. |
~08:40 | ~4:30 | Tunnel reports healthy, so no rollback. Our side is done. |
08:48:10 | 12:32 | The .fyi registry publishes the delegation. The name now resolves worldwide, and HTTP works. |
08:52:43 | 17:05 | Cloudflare validates and issues the Let's Encrypt certificate. Live over HTTPS. |
About three-quarters of the time was waiting on third parties: the registry and the certificate authority. Everything that needed doing took under five minutes.
Earlier the same morning
Bastion reboot test. It came back on its own, with the tunnel, SSH and the Claude Code session all up.
A scoped Registrar API token was added to the bastion's secrets file and tested with read-only calls.
One name was price-checked against roughly 400 extensions.
.fyi won on total cost.