How this site is built

A single virtual server hosts this site and every project alongside it, behind one web server, maintained conversationally by an AI agent running on the same box.

The idea

Most personal projects die in the gap between "it works on my laptop" and "it is on the internet." The goal here was to close that gap permanently: one server, one address, and a repeatable path from idea to live URL that does not involve remembering how any of it works.

The result is an additive host. New projects are added; nothing is torn down to make room. Each one is reachable under the same address at its own path.

Architecture

                        internet
                            |
                    +-------+-------+
                    |  ufw firewall |   22, 80, 443 only
                    +-------+-------+
                            |
                    +-------+-------+
                    |     Caddy     |   sole listener on 80/443
                    +---+-------+---+
                        |       |
         +--------------+       +--------------+
         |                                     |
   +-----+------+                      +-------+--------+
   |  static    |                      |  dynamic apps  |
   |  files     |                      |  127.0.0.1:70xx|
   |  /srv/www  |                      |  systemd, non- |
   |            |                      |  root user     |
   +------------+                      +----------------+

   -- separate, loopback only -----------------------------
   +----------------+        +------------------+
   | agent gateway  |<------>|  chat interface  |
   |  127.0.0.1     |        |   (allowlisted)  |
   +----------------+        +------------------+

The stack

Host
Hetzner VPS, 2 vCPU / 4 GB
Cheap, predictable, full root access. Resizes in place without a rebuild.
OS
Ubuntu 26.04 LTS
Long support window; broadest compatibility with tooling.
Firewall
ufw
Default-deny inbound. Three ports open, nothing else.
Web server
Caddy 2
One process owns 80/443 and routes everything. Automatic HTTPS the moment a domain points here.
This site
Astro (static output)
Emits plain HTML. No runtime process, no memory cost, nothing to crash.
Content
JSON files
Structured data rather than markup, so it can be edited programmatically without degrading.
Agent
OpenClaw + Claude
Runs as a service on the same host; reached over a chat channel.

How a request is served

Caddy matches the most specific path first and falls through to the root site last. Static projects are served straight off disk; dynamic ones are proxied to a local port that is never exposed publicly.

redir /projectname /projectname/ 308
handle_path /projectname/* {
    root * /srv/www/projectname
    file_server
}

# dynamic equivalent
handle_path /appname/* {
    reverse_proxy 127.0.0.1:7001
}

The distinction that matters is handle_path rather than handle: it strips the path prefix before the request continues. Without that, an application mounted at /projectname receives /projectname/asset.css when it expects /asset.css, and every asset reference breaks.

Adding a project

The procedure is deliberately identical every time:

Nothing about this requires taking an existing project offline.

The agent layer

An OpenClaw gateway runs as a service on the same host, backed by a Claude model. It is reached through a messaging channel, which makes the deployment procedure above something that can be requested in a sentence rather than performed by hand.

This is the part with real teeth, so it is worth being precise about the trade-off. An agent that can deploy software is an agent with a shell. The controls that make that acceptable:

The honest caveat: instructions given to a language model are guidance, not a permission boundary. Content fetched from the open web is treated as untrusted, and anything that genuinely must not happen is enforced by file permissions and firewall rules rather than by asking nicely in a prompt.

Deliberately not here yet