Platform · J.2 What it runs on
Cluster architecture
DesignThe one-node, two-namespace cluster the pipeline is designed to run on: its seven control-plane services, how code is built and deployed without Docker, and where the data lives.
J.2.17 min readLast revised November 2026
Status: Design. A specification. It describes how the component is meant to work, not something you can use on this site today. What the labels mean.
On this page
The pipeline is designed to run on a single small Kubernetes node, with everything it needs inside the cluster: its own Git server, its own image registry, its own build runner and its own deployer. This page describes that design.
One node, two namespaces, seventeen pods#
The whole platform is sized to fit on one machine with 4 GiB of memory, running k3s with containerd.
| Namespace | What runs there | Memory requested |
|---|---|---|
clearedstack | The control plane: seven services | About 640 MiB |
cs-workers | The pipeline: ten workers, plus short-lived browser jobs when a page needs one | About 300 MiB |
Under 1 GiB at rest leaves room for the operating system, the database’s storage and the occasional build.
An earlier draft gave every worker its own namespace, eleven in all. Two keeps the network rules simple and the manifests few, which matters to one person maintaining the stack.
The control plane#
| Service | What it does | Memory requested |
|---|---|---|
| Conn | Serves the public site. The only service that can be reached from outside | 128 MiB |
| Muster | Hands out work to the workers, records results and receives edit proposals | 48 MiB |
| Logbook | The one database, on a persistent volume. Logbook is the record itself; today it is kept in PostgreSQL | 256 MiB |
| Plainforge | Holds every repository, signals each push and mirrors code to GitHub | 64 MiB |
| Lazarette | Stores built images inside the cluster and keeps the last five of each | 64 MiB |
| Keelworks | Starts a short-lived job to test and build on each push | 32 MiB |
| Shipkeeper | Deploys what was built, watches its health and reports it | 48 MiB |
Plainforge, Lazarette, Keelworks and Shipkeeper are written from scratch as small Go programs instead of being assembled from large existing projects. Clean room and credit explains why.
From a push to a running pod#
Every change follows the same path, and no step uses Docker.
- PlainforgeA commit landsOn main or as a tag. Plainforge mirrors the code to GitHub and tells Keelworks.
- KeelworksA build job startsA temporary, unprivileged job is created for this one build.
- Build jobTest, build, signThe repository’s tests run first. Then the image is built, signed and pushed to Lazarette.
- KeelworksRecord what was builtThe image’s digest is written into the app’s manifest in Plainforge.
- ShipkeeperDeployShipkeeper sees the new digest and rolls the pods forward one at a time.
Why no Docker. A Docker daemon runs with root-level privileges and uses memory a small node cannot spare. Building images inside a cluster with Docker usually means giving the build job control of the host. k3s already runs containers with containerd, so all the cluster needs is a standard image, and there are builders that produce one without a daemon:
| Builder | Used for | How |
|---|---|---|
ko | Everything written in Go: Plainforge, Lazarette, Keelworks, Shipkeeper, Muster and the ten workers | Compiles the source straight into a minimal image, with no build file and no daemon |
| Rootless BuildKit | The website | Builds the image from the repository’s Containerfile inside an unprivileged pod |
Tests come first. A build job runs the repository’s tests, including the saved examples for every sub-tool, before it builds anything. Code that fails never reaches the registry.
Images are signed. Each image’s digest is signed with cosign before it is stored.
Code on GitHub, images inside#
Plainforge is the primary copy of every repository. It sits inside the cluster and cannot be reached from the internet. Everything that writes code talks to it first: an approved edit, a task for a missing rule, a new image digest. On each change it pushes the code outward to GitHub, so that anyone can read, check or fork it.
GitHub is a mirror of the code only. Built images never leave the cluster.
| GitHub | Lazarette | |
|---|---|---|
| What is there | Source code and tags | Signed images |
| How it gets there | Pushed outward by Plainforge on each change | Pushed by a build job, inside the cluster |
| Rolling back | Would need a rebuild | Immediate, from the last five images kept |
Keeping images inside means no registry passwords in the worker pods, and a rollback that works even when the outside network does not.
Install, upgrade, pause, roll back#
Shipkeeper reads each app’s manifest from Plainforge and applies it to the cluster with Kubernetes’ own server-side apply. It adds no custom resource types. For the owner it offers four actions on any service or worker:
- Install applies the app’s manifests for the first time.
- Upgrade moves it to the newest signed image.
- Pause scales it down without deleting its configuration or data.
- Rollback returns it to one of the five previous images kept in Lazarette.
It also watches the health of every pod and reports it, after removing anything that would reveal the layout of the cluster. See what stays private.
One database and one gatekeeper#
All state lives in a single Postgres database: the items, the queue, the decisions and the edit proposals. Keeping the queue and the state in one place means a change of state and the next step in the queue happen together or not at all. An item is never marked done in one system while still waiting in another.
The data is split in two. core holds the full working records. pub holds views of them with only the columns meant to be public.
| Service | Access | Why |
|---|---|---|
| Muster | Reads and writes core | It is the only gatekeeper in front of the database |
| Conn | Reads pub only | The public site cannot reach a private field, whatever page is requested |
| The ten workers | None | They hold no database password and cannot reach the database on the network |
Workers ask Muster for work over the cluster’s private network and identify themselves with their Kubernetes service account. A worker that reads untrusted public pages therefore has no database credentials to leak. Only Muster knows the layout of the tables, so a change to a table does not ripple through ten repositories.
Nothing finished is lost. Each worker has a small disk of its own. When it finishes a step it writes the result there first, then reports it. If Muster is restarting, the result waits on the worker’s disk and is sent again until it is acknowledged. Unfinished work is covered the other way round: the worker’s lease on the item runs out and the item is offered again.
Network, priority and privilege#
Closed unless opened. Both namespaces block all traffic by default. Only Conn accepts connections from outside. Workers may call Muster, and the workers that read public sources may fetch them from outside. Workers cannot reach the database, Plainforge or Lazarette.
Who keeps running under pressure.
| Priority | Workloads | When memory is short |
|---|---|---|
| High | The database, the queue service, the website, Plainforge, Lazarette, Shipkeeper | Kept running, so the site and the queue stay up |
| Standard | The ten workers | Give way before the control plane does |
| Low | Build jobs and browser jobs | Run when there is room, and are stopped first |
No root. Every pod runs as an ordinary user, with all Linux capabilities dropped, no way to gain privileges and a read-only filesystem wherever nothing needs to be written.
Starting from nothing#
The design calls for the cluster to set itself up from one manifest applied to a fresh k3s node. A setup job that runs once then:
| Step | What it does | Why |
|---|---|---|
| Secrets | Generates the database passwords, service tokens, signing keys and the salt used to hash visitor addresses | Credentials are created inside the cluster and never appear in Git |
| Database | Creates the core tables and the pub views | Private data and public data are separate from the first minute |
| Repositories | Creates a repository in Plainforge for each service and worker | The cluster has its own source of truth |
| First builds | Has Keelworks build and sign every image | Nothing is pulled ready-made from outside |
| Network rules | Applies the rules that leave only the website reachable | The database, Git, the registry and the workers are closed from the start |
Because every repository is MIT licensed and written from scratch, the design can be forked without asking: rebranded, given different scoring weights, or run privately with the GitHub mirror turned off.
Running it with no internet connection#
Nothing in the design depends on an outside service while it runs, so the whole cluster can sit inside a closed network. Four things change.
| Part | Connected | Disconnected |
|---|---|---|
| Plainforge | Mirrors code to GitHub | The mirror is off. All code stays inside |
| Tidegauge | Checks public regulation feeds | Paused, or pointed at copies kept inside |
| Sextant | Fetches public pages | Reads only what can be reached inside, or files brought in |
| Lazarette | Stores images built by Keelworks | The same. Nothing is pulled from outside |
What crosses the boundary is a bundle of signed image archives, the manifests and the repositories, with a checksum to prove it arrived intact. Working on disconnected networks covers that discipline.
Found a mistake, or something that has gone out of date? Open an issue or write to [email protected]. This page is a text file you can read and correct: Source of this page.