How websites get published to the personal AWS Lightsail host, why every hostname resolves to a single IPv4 address, and how two AI agents share the machine without stepping on each other.
The constraint that shapes everything: the intended audience sits behind a corporate firewall that whitelists destinations by IPv4 address, not by domain name. A page is only genuinely reachable if every resource the browser must fetch lands on the same whitelisted address.
So the topology is deliberately old-fashioned — one Lightsail instance, one attached static IP, many hostnames served by Caddy through SNI virtual hosts. No CDN, no proxy, no Cloudflare orange cloud, because each of those would replace the destination IP and silently break the whitelist.
Critical assets — scripts, stylesheets, fonts, images, media, iframes — must come from the same host or another name resolving to the same IPv4. A single fonts.googleapis.com link is enough to break the page for the people it was built for. Ordinary hyperlinks are fine; anything the browser must load is not.
| Component | Detail |
|---|---|
| Instance | codex-static-sites-micro |
| Static IP | codex-static-sites-ip → 18.138.156.135 |
| DNS | Wildcard *.falconshire.com A record straight to the static IPv4. No AAAA — an IPv6 answer would divert an IPv4-only client. |
| Hostnames | <slug>.falconshire.com, derived server-side from a validated DNS label |
| Apex | falconshire.com issues a 301 to the primary library |
| Releases | /srv/static-sites/sites/<slug>/releases/, with an atomic current switch |
| Firewall | TCP 80 and 443 open; TCP 22 public-key only, no root login, no passwords |
Publishing never touches the web server configuration by hand. The client packages a build; the server validates it independently and installs it.
index.html. Symlinks, device files, .git, .env*, private keys and package caches are rejected outright, as is any critical asset whose origin does not resolve to the whitelisted IPv4.incoming directory. That account can do nothing else.static-site-admin, is the only privileged entry point.current is flipped, the Caddy config is validated, then gracefully reloaded. A global lock serialises this, so two agents publishing at once is safe.The design assumption is that the publishing client may be wrong or compromised, so privilege is kept off it.
codexdeploy account with no standing privileges.sudo permits exactly one command — the publish wrapper. Nothing else.BatchMode=yes and StrictHostKeyChecking=yes always. Host-key checking is never relaxed to make a deploy succeed.The practical consequence: path-based routing such as /some-page under the apex is not available to the publisher, and adding it would mean hand-editing root-owned config. A new subdomain costs nothing instead — the wildcard record already resolves it.
Two AI coding agents publish to this host. The slug namespace is flat and shared, so identical slugs would overwrite each other's sites. That is resolved by reserving a prefix.
| Namespace | Owner | Index |
|---|---|---|
| claude* | Claude Code | claude.falconshire.com |
| everything else | Codex | site-library.falconshire.com |
Each agent's catalogue enumerates the server directly and filters to its own namespace, so neither library lists the other's sites and neither needs a hand-maintained list. Each agent is also barred from publishing, rolling back or deleting anything in the other's namespace.
Both libraries read the live server rather than a checked-in list. A site that exists appears; a site that is removed disappears. There is no state to drift out of sync, and no shared file for two agents to edit concurrently.