The central application
The one system that decides what happens. It keeps a key pair per machine, signs every task for exactly one machine, pushes it into Valkey and waits on a reply key. It never opens a connection to a server.
One central application commanding many machines — without opening a single port on any of them. Two Go services make it work: a node agent on every machine that runs only Ed25519-signed tasks, and a WebSocket relay that holds each machine's outbound connection and carries commands and replies through Valkey.
Small on purpose: about 1,400 lines of Go between the relay and the agent. The interesting part is the contract between the three applications — who may sign what, how a machine is claimed, and what the relay promises so the central system never hangs.
A central system has to run work on a growing number of servers — deploy, configure, read and write files — and know which of them are reachable right now.
Listening for commands means a firewall rule, a public address and a reason to trust whoever connects — on every machine.
Whatever runs commands as root should accept a small, well-defined set of tasks, and only from the one system allowed to send them.
The one system that decides what happens. It keeps a key pair per machine, signs every task for exactly one machine, pushes it into Valkey and waits on a reply key. It never opens a connection to a server.
A small Go service that holds every machine’s long-lived WebSocket. It moves commands out and replies back through Valkey and keeps each machine’s presence current. It holds no signing keys, so it cannot forge a task.
A Go service on every machine. It dials out to the relay, reconnects on its own, and runs a task only after its signature and claims check out — then replies over the same connection.
The central application builds a task for one machine and signs it with that machine’s Ed25519 key. The issuer, audience and the machine’s own ID are part of what is signed.
It pushes the signed task onto that machine’s command list in Valkey, then waits on a reply key named after the command.
The relay’s worker for that machine pops the command and sends it down the machine’s open WebSocket.
The agent checks the signature against the central application’s published keys, checks that the claims name this machine, runs the task and sends the result back.
The relay matches the reply to its command ID and stores it for 60 seconds; the central application’s wait ends with the result — or with a clear failure if anything went wrong on the way.
A newly installed agent only knows a built-in setup key, and accepts exactly one setup call signed with it.
That call registers the machine’s ID and a new per-machine Ed25519 public key generated by the central application.
Setup is disabled for good. From now on the machine runs only tasks signed with its own key and addressed to its own ID.
Over the relay, the agent verifies against the central application’s published key set and picks the key by ID, so keys change without touching the machine.
Each machine has a presence flag that lives 30 seconds and is refreshed every 20. The central application reads it to know which machines it can reach right now.
Every machine has its own worker in the relay, so the fleet works in parallel — while commands for any one machine run strictly in order, so deployment steps never overtake each other.
Because each queue is separate, one busy or slow server never holds up the rest of the fleet.
A machine that reconnects replaces its old connection, and the old connection closing cannot mark the machine offline.
A failed delivery, a timeout or a disconnect still produces a failure reply, so the central application’s wait always ends.
Pending replies are tracked by command ID; a late answer to a command that already timed out is dropped rather than misdelivered.
Command delivery and heartbeats share a lock, because a WebSocket allows only one writer at a time.
The central application and the relay agree only on Valkey key names and message shapes, so either side can be redeployed independently.
No listening port, no firewall rule, and it works behind NAT.
The central application never holds a socket; the relay can restart without breaking the contract.
The relay only moves messages; compromising it cannot produce a valid task.
A task signed for one machine is useless on any other.
A shared setup key can claim a machine exactly once; after that, only that machine’s own key works.
Four task types are easier to reason about than a general remote shell API.
Steps arrive in order on each machine; one slow machine cannot block the others.
A caller that can hang is worse than one that gets an error.
One application can run work on any number of machines and knows which are online.
Machines connect out; nothing on them listens for commands from the internet.
Every task is signed for one machine and verified there before it runs.
In-order per machine, parallel across the fleet, and every command gets an answer.
I designed, built and coded the system: the contract between the three applications, the per-machine signing and claiming model, the agent’s task set and execution rules, the relay’s queue contract and concurrency guarantees, and the central application’s side in Laravel. The relay ships as a static binary in a small container image, the agent as a systemd service.
Built for an internal product — product and infrastructure identifiers are deliberately omitted.
Shipped to production. Licences verify offline, so a vendor outage never stops a paying customer, and support moved from engineering to audited operator actions.
Read the case study →SaaS · Cloud marketplace · Server automationv1 shipped February 2025. Sites, SSL, PHP, databases, backups and WordPress itself are run from the browser, with Stripe billing on top.
Read the case study →Next