jamalbuilds.fyi · a working demo

How this site got built.

The domain was bought through an API, the server has no open ports, and every change went through an ops box that is only reachable through Cloudflare. Here is the whole path, with the real commands.

$5.20domain, per year
0inbound ports open on either server
17 minfrom "yes, buy it" to a live HTTPS page
2small VMs, no root used

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.

Visitors reach Cloudflare's edge, which forwards through an outbound tunnel to the web server. The operator works from a phone through the bastion host, which reaches the web server through its own tunnel. Cloudflare (global edge) DNS + TLS + proxy jamalbuilds.fyi resolves to Cloudflare, not the server Access (identity check) operator SSH must log in or show a service token Registrar + API domain bought, zone created, records added You, the visitor any browser Operator's phone Claude Code Remote Control Bastion (ops box) 1 vCPU · 1 GB · Ubuntu holds keys + API tokens Web server Rocky Linux · SELinux rootless nginx container listens on 127.0.0.1 only tunnel tunnel SSH via Cloudflare API calls Both servers dial out to Cloudflare. Nothing on the internet can dial in.
visitor requestoperator / adminCloudflare API
Visitor

You never see the server

DNS points at Cloudflare. Cloudflare terminates HTTPS and forwards the request down a tunnel the server opened itself.

Bastion

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.

Web server

Containers, not root

Each site is a rootless container run by an unprivileged user and supervised by systemd.

Edge

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.

  1. 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. .fyi was $5.20 for the first year and $5.20 every year after.
  2. 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.
  3. 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.
  4. 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:

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
ChoiceWhy it matters
Rootless PodmanThe container runs as an unprivileged user. A break-out lands in a normal account, not root.
systemd user unitsStart at boot and restart on crash, with no root needed to install or manage them.
Bound to 127.0.0.1Only the local tunnel daemon can reach the site.
Read-only mount, :z labelnginx can't modify the files. SELinux stays in enforcing mode and the label lets the container read them.
Deploy = rsync + restartThe 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.

NameTypeTargetProxy
jamalbuilds.fyiCNAME<tunnel-id>.cfargotunnel.comon
wwwCNAME<tunnel-id>.cfargotunnel.comon
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.

The same pattern scales down to nothing: point the tunnel at a laptop, and that laptop serves a public HTTPS site without a public IP or a router port-forward.

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.

~4 minall the hands-on work
~9 min.fyi registry publishing the domain
~4.5 minCloudflare issuing the certificate
17 mintotal, approval to HTTPS
Time+Step
08:35:380:00Operator approves. Price re-checked ($5.20), registration call sent.
08:35:480:10jamalbuilds.fyi registered. DNS zone already live on Cloudflare.
08:38~2:30Page written, rootless nginx container started as a systemd user service.
08:38:232:45Tunnel routes applied through the guarded helper (validate, back up, restart).
08:39~3:30Two proxied CNAMEs created by API.
~08:40~4:30Tunnel reports healthy, so no rollback. Our side is done.
08:48:1012:32The .fyi registry publishes the delegation. The name now resolves worldwide, and HTTP works.
08:52:4317:05Cloudflare 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.