Brandon Seamer

A platform for a robotics program

The primary node carries one platform that is not for the household: a place where the teams of a youth robotics program build and run their own web applications. The operator designed it, built it and wrote its manual. This page sets out what it was asked to do, the path a request and a change each take through it, how one team is kept from another, and what was chosen over what.

It was built in October 2026 and proved with test teams. No team has been moved onto it yet, and two parts are not built; both are named at the end.

What it was asked to do

  • Host several teams, each with several applications of its own: a scrum board, an engineering notebook, a calendar. A team has one address, and each application is a path under it.
  • Make a student the administrator of their own team's applications, releases and data, and of nothing that belongs to another team.
  • Give a mentor every power, a shell on the machine included.
  • Admit none of them to the house's network or to the estate's private mesh. They arrive from the internet.
  • Be run by people who did not build it. One person in the program has software experience, teaches the rest in limited time, and is not always available.
  • Use the systems a student meets again in industry, at no monthly cost.

What a request goes through

  1. Cloudflare answers the visitor and holds the certificate. Where the server is appears nowhere.
  2. One tunnel carries the request to the machine. The machine opened it outwards; nothing in the house waits for a connection from outside.
  3. One reverse proxy, Caddy, reads the name and the path. A team's address and an application's path select one port. A name nobody has listed as public asks for a mentor's password.
  4. That team's containers answer on the port, in a Docker service that belongs to the team alone.
  5. PostgreSQL holds the application's data, in a database only that team's roles may open.

What a change goes through

  1. A student pushes to their team's repository on GitHub.
  2. GitHub Actions builds and tests the commit.
  3. The machine asks the repository whether anything moved. It pulls; nothing outside it holds a credential into it, and no pipeline can run a command on it.
  4. Work merged to the main branch is released to the team's preview address, which is closed to the public. A version tag is released to the live address, so going live is a deliberate act with a number on it.
  5. The release is built and started as the team's own user, and each application must answer its health address.
  6. A release that does not answer is not kept. The previous release is started again, the failure is written where the team can read it, and that commit is not tried again until a new one is pushed.

Three walls

Each wall was proved from the inside, by attempting the act it forbids, with a control beside every attempt to show the test itself could succeed.

BetweenWhat it is made ofHow it was proved
The platform and the houseA virtual machine on a network of its own. The host drops every packet the machine sends to a private address before routing looks at it, so the machine reaches the internet and nothing else: not the host, not another container, not the household network, not the private mesh.A root process inside the machine tried fourteen addresses in the house that were known to be listening. None opened, the drop counters moved, and public addresses opened. Repeated after the mesh's agent was restarted on the host and after a cold start.
One team and anotherEach team is an unprivileged user with a rootless Docker service of its own. A container told to mount the whole disk can read only what that user can. A firewall inside the machine lets only a team's user answer on the team's ports, and lets a team's containers reach nothing on the machine but the database.From inside one test team's containers, the other team's files were refused, a privileged container included; its ports timed out; a listener placed on one of its ports was never heard.
One team's data and another'sA database role per team that owns the team's databases and may create roles for its own students, and only those.A test team's role was refused the other team's database and refused a change to the other team's role, and made and granted a student role of its own.

What was chosen, and over what

PartChosenSet aside, and why
Where applications runDocker, with a rootless service per teamA shared Docker service: whoever defines a container on it can read every team's files. Kubernetes: it separates teams well, but one person here has run it, and no student would have read a line of it. A virtual machine per team: the strongest wall, and five machines to patch.
DatabasePostgreSQLMicrosoft SQL Server: the free edition caps the size of a database below what the teams expect to store, and the paid licence costs thousands of dollars.
ReleasingGitHub and GitHub Actions; the machine pullsA pipeline that pushes to the server keeps a credential into it somewhere else. A pipeline runner on the machine runs anything a pipeline file says, beside every other team. Azure DevOps charges a seat for each person after the fifth.
AddressesA name per team, a path per applicationA name per application needs a certificate two levels below the domain, which is a monthly charge.
ApplicationsASP.NET Core, started from one templateA stack per team. One person teaches all of it, and the template already meets the standard: settings from the environment, no data inside a container, versioned database migrations, a health address, a preview and a live copy.
PeopleTwo levels, student and mentorA level between them. The program is small, and whoever helps must be able to repair it when the operator is away.

What building it found

  • A test that proved nothing. The first test of the wall between teams passed on every row, because its web server had never started and every port was closed to everyone. The control rows failed with it, which is what the controls were for.
  • A release put back by hand came forward again. The automation saw the branch ahead of what was running and released the faulty commit a second time within the minute. A commit that was put back from is now held until the branch moves.
  • An administrator nobody owned. The operating system's image created a default account with administrator rights. It had no password and no key, and it was found only because a test listed who held those rights.
  • A prediction, measured. Restarting the mesh's agent on the host moves its packet-filter rules ahead of the estate's own. That had been read from the rules and never observed. The platform's rules had been placed where order cannot matter, and they held through it.

Written for a second operator

The platform has a manual of its own: a map, a page for each part, a runbook for each routine job and each fault, the decisions with what each one set aside, and a glossary for a beginner. Every command in it was run on the machine, and every description of a healthy part is output that was read. It is written for a mentor working with an AI assistant, and opens with what must never be done: a team's application or data changed without that team.

What is not built

A mentor's shell from outside the house waits on a sign-in service in front of the tunnel. A team reads its last release and its logs on a page under its preview address; a fuller control panel in the browser is not chosen. The backups are taken every night and kept in two places, both in the house.

architecture view

Open the SVG in a new tab.