Skip to content

The website loop

This is golem deploying golem’s own docs. Build this Starlight site into a container on one golem-managed box, push it to the golem-hosted registry, run it as a Workload on a third, and curl the homepage for a 200.

It continues A registry on the fleet — the registry, builder, and web guests must be up, golemd deployed, and the registry serving on your 127.0.0.1:5000. Commands are nushell, from the repo root.

The three boxes

BoxscrollRole
kaijuexamples/registry/registry.emetstores the image
talosexamples/website/builder.emetbuilds the site image and pushes it
remoraexamples/website/website.emetpulls the image and serves it publicly

builder.emet provisions podman and the insecure-registry drop-in — the box can push, but building and pushing is a CI step, not a glyph. The scroll only makes the box able to push.

website.emet runs the served container. It drops in the same insecure-registry fragment, then a single public Workload — the golem-website image pulled from the fleet registry, published on :80:

import Quadlet exposing
( image, tcp, Restart(..), Expose(..), Workload(..), workloadGlyphs )
registryEndpoint : String
registryEndpoint = "10.0.2.2:5000"
-- insecureConfPath and insecureConfContents elided: the drop-in's path under
-- /etc/containers/registries.conf.d, and its [[registry]] … insecure = true body.
website : Workload
website =
Workload
{ name = "golem-website"
, image = image registryEndpoint "golem-website" "latest"
, env = []
, ports = [ tcp 80 80 ]
, volumes = []
, restart = Always
, expose = Public
}
websiteGlyphs : List Glyph
websiteGlyphs =
List.concat
[ [ file
{ path = insecureConfPath
, contents = insecureConfContents
, mode = "0644"
}
]
, workloadGlyphs website
]
main : List Scroll
main =
[ scroll
{ name = "web"
, notifies = [ "golem-nftables.service" ]
, glyphs = websiteGlyphs
}
]

expose = Public opens :80 to the world with one drop-in file, and pulls in the nftables base that loads it, so the web scroll lowers to eleven glyphs:

main : List Scroll
planned scrolls (1):
scroll `remora` (11 glyphs):
* ensure file `/etc/containers/registries.conf.d/golem-registry.conf` (mode 0644)
* ensure apt package `podman` installed
* ensure file `/etc/containers/systemd/golem-website.container` (mode 0600)
* enable + start systemd unit `golem-website.service`
* ensure apt package `nftables` installed
* ensure directory `/etc/nftables.d` (mode 0755)
* ensure file `/etc/golem-nftables.conf` (mode 0755)
* ensure file `/etc/nftables.d/00-base.nft` (mode 0644)
* ensure file `/etc/systemd/system/golem-nftables.service` (mode 0644)
* enable + start systemd unit `golem-nftables.service`
* ensure file `/etc/nftables.d/public-golem-website-80.nft` (mode 0644)
↻ remora notifies golem-nftables.service

The last file is the whole opening: a complete table inet golem block accepting :80, dropped into a directory the entrypoint conf includes. The six glyphs above it are the base every exposed workload carries — the table, its entrypoint, and the service that loads them. The line is the scroll’s notifies: one reload at the end of the apply, after the drop-in is in place.

Run the loop

  1. Build the site on your workstation.

    Terminal window
    build-site

    The devenv script that runs bun run build in sites/website. It ends on Astro’s own summary and leaves the output in sites/website/dist:

    [build] Complete!
  2. Ship the build context to the builder and push the image. This is the long step: the builder pulls a base image, runs the container build, and pushes every layer over the host gateway. Minutes, and mostly silent between layer lines.

    Terminal window
    tar -C sites/website -cf - Containerfile nginx.conf dist | fleet ssh talos -- "mkdir -p site && tar -xf - -C site"
    fleet ssh talos -- "sudo podman build -t 10.0.2.2:5000/golem-website:latest site && sudo podman push 10.0.2.2:5000/golem-website:latest"

    The quoted && chains run in the guest’s bash over ssh, so nushell never sees them. podman build ends on the image id it just wrote, and the push ends on its digest:

    Writing manifest to image destination
  3. Check that the registry took it.

    Terminal window
    curl -s http://127.0.0.1:5000/v2/_catalog
    {"repositories":["golem-website"]}

    That request went to your workstation’s 127.0.0.1:5000, which qemu forwards into the registry guest. The builder reached the same registry from inside its own guest at 10.0.2.2:5000 — the host gateway — which is how two SLIRP-isolated guests trade an image with no shared network between them.

  4. Serve it. The web box pulls the freshly-pushed image and starts serving:

    Terminal window
    fleet plan examples/website/website.emet --hosts remora
    fleet apply examples/website/website.emet --hosts remora

    The plan is eleven installs against revision 1; the apply draws them settling and closes on one line under the host’s heading:

    web ssh://golem@127.0.0.1:2252
    apply settled — revision 2
  5. Curl the homepage for a 200. The web box’s :80 is published to your workstation on 8081:

    Terminal window
    curl -sI http://127.0.0.1:8081/ | lines | first
    HTTP/1.1 200 OK

Golem’s documentation, built on a golem-provisioned builder, stored in a golem-provisioned registry, pulled and served by a golem-provisioned web box. curl -s http://127.0.0.1:8081/ shows <title>Golem | Golem</title> in the markup.

Explore from here

  • Re-deploy a docs change. Edit a page, rebuild, push a new :latest, and re-apply the web scroll — the .container glyph’s content id is unchanged (the tag is the same), so pin a new tag or a digest with imageAt to make the diff replace it.
  • Reverse the web box. Apply a web scroll with an empty glyphs list and watch golem stop the container, remove the quadlet, and close :80 by deleting its one drop-in file — leaving every other opening’s file untouched.
  • Add a second served site. Write another public Workload on a different port and apply it alongside — two containers, two firewall openings, derived from their ports.

Where to next