sklet — the node agent
sklp talks to a node, sklet is a node
sklet is the node-side agent. It supervises the spaces deployed on its machine,
runs the reconcile loop, and serves the control API that
sklp deploy pilots from a workstation. It ships without the
dev, CI and publish surface: a production node has no use for a pipeline engine.
| sklp | sklet | |
|---|---|---|
| Runs on | the workstation | the node |
| Does | apply, inspect, logs, exec, contexts, login | supervise, reconcile, serve the control API |
| Reaches the node | over the control API | is the node |
First-time setup of a node is in Deploy on a VM.
Verbs
| Verb | Purpose |
|---|---|
apply | Apply .sklp/<stack>/deploy.yaml on this node. |
agent | Run an empty control-plane node waiting for a remote deploy. |
reconcile | Pull the repos this node deploys from and apply what changed. |
host setup | Check or apply the rootless host prerequisites. |
host configure | Render the sklet-agent unit from node.yaml and restart it. |
token rotate | Replace the control API token. |
gateway mux | Run the ingress-per-space front door. |
space migrate-owner | Hand a stopped space's volumes to its own root, or back. |
version | Print the binary version. |
gc | Remove images this node no longer runs. |
upgrade | Install a published sklet and restart the agent. |
apply
sklet apply # no stack: list the choices
sklet apply core --space production
sklet apply core --foreground # debug: supervise in this terminal| Flag | Default | Meaning |
|---|---|---|
--space | production | Space to deploy (named or ephemeral). |
--repo | . | Repository root. |
--foreground | Supervise in this terminal instead of detaching. | |
--listen | Also expose the control API on this TCP address (TLS + Bearer). | |
--force | Adopt a space deployed by a different stack. | |
--gateway-mode | singleton | per-space (native mux) or singleton (traefik). |
agent
The normal mode of a production node: open the control API and wait for
sklp deploy apply. Configured through node.yaml and rendered into the unit by
sklet host configure — not by hand.
| Flag | Meaning |
|---|---|
--listen | Control API address, default :6464. Never :9080/:9443 (the front door). |
--gateway-mode | Required: per-space or singleton. Never inferred. |
--advertise | Public host/IP written into the generated node config. |
--advertise-via | On a private origin: advertise https://<node-id>.<domain>:<port>. |
--node-relay-domain | On a front: relay control API connections by SNI to mesh peers (ADR-019). |
--mesh-authority-url | meshauthority URL; empty disables the mesh. |
--mesh-endpoint | This node's dialable IP:port for WireGuard. |
--mesh-cert-fingerprint | meshauthority certificate SHA-256, required for https://. |
--public-nic-interface, --public-nic-source, --public-nic-table, --public-nic-mark | Keep a public NIC's policy routing and SMTP egress in place. |
Mesh flags are detailed in The mesh.
reconcile
The node's pull loop (ADR-014). It polls every repo in reconcile.yaml and
reconciles those whose branch moved. Nothing reaches the node, so a node behind
NAT works the same — a push lands within the poll interval.
# ~/.config/sklp/reconcile.yaml
interval: 1m # floored at 10s
targets:
- repo: https://<token>@github.com/acme/app.git
branch: main # default
name: app # default: last path element| Flag | Default | Meaning |
|---|---|---|
--config | $XDG_CONFIG_HOME/sklp/reconcile.yaml | Config path. |
--once | One pass, then exit. | |
--space | production | Picks which host-wide control credentials file to use. |
--control-url | https://127.0.0.1:6464 | Control API applied against over loopback. |
No file, or no targets, means the node pulls nothing and only serves what an operator applied.
host
sudo sklet host setup # one-time: uidmap, subuid/subgid, linger, launcher
sklet host setup # unprivileged: check only
sklet host configure # after any edit of node.yaml / node.env
sklet host configure --dry-runhost configure (ADR-020) renders the sklet-agent user unit from
~/.config/sklp/node.yaml (flags and node-owned settings, parsed strictly) and
~/.config/sklp/node.env (secrets, 0600, loaded through EnvironmentFile=),
reloads systemd, enables and restarts the agent. It is idempotent and
unprivileged. node.yaml keys include listen, gateway_mode, advertise,
advertise_via, node_relay_domain, data_root, acme_email,
otlp_endpoint, mesh, policy (trusted_registries, require_image_digest,
acme_storage_dir) and public_nic.
token
sklet token rotateReplaces the control API token and prints the new one; the old one stops working
immediately. From a workstation, prefer
sklp deploy token rotate, which also
saves it into the context.
gateway
sklet gateway mux --listen :9080 --listen-tls :9443Routes external traffic to each space's ingress — by Host header on HTTP, by SNI
(passthrough) on TLS. An empty --listen-tls disables HTTPS.
space
sklet space migrate-owner <space>
sklet space migrate-owner --back <space>Moves a stopped space's volumes to its own root (child user namespace), or
back to the operator with --back. Only the operator's files move. Refused while
the space may be running.
version and gc
sklet version # -s / --short for the bare tag
sklet gc --dry-runThe agent already garbage-collects images hourly and after every apply; gc runs
it by hand.
upgrade — the only restart path
sklet upgrade # latest published, health-checked
sklet upgrade --version <tag>
sklet upgrade -force # reinstall and restart, even on the same version| Flag | Meaning |
|---|---|
--version | Version to install (default latest). |
--dist | Distribution base URL. |
--force | Reinstall and restart even when already on that version. |
upgrade verifies the download against SHA256SUMS, swaps the binary, restarts
the agent and rolls back if it does not stay up. No sudo once the launcher is
installed by host setup.
sklet upgrade -force is the only way to restart an agent. Never
systemctl restart, pkill or kill it: the agent supervises runc containers,
overlay mounts and its own state, and an outside kill desyncs all of it. Check
that the latest published version is the one running before forcing.
Control API
| Transport | Address | Auth |
|---|---|---|
| Unix socket | per space, parent dir 0700 | file permissions |
| TCP | :6464 (agent --listen) | TLS (pinned self-signed cert) + Bearer token |
sklp deploy reaches the TCP listener with a context holding the server, token
and CA fingerprint. Behind a front, the control API is reached through the
front's SNI relay (Front and private origins).
Supervision
sklet apply and sklet agent detach a supervisor (sklet __supervise, internal)
under systemd --user when available, else as a daemonless setsid session.
The terminal is freed either way; --foreground keeps it attached for debugging.
Lingering, enabled by host setup, keeps the user units alive without a login.
Child user namespace per space
Each service runs as a root of its own (ADR-022): its holder is spawned in a
child user namespace of the space worker's, so container uid 0 maps to a
subuid that owns nothing on the host. One root per space, taken from the
operator's second delegated subuid range (a persistent slot in
child-roots.json); the service's other uids keep the same host owners. Existing
volumes move with sklet space migrate-owner. This is the wall behind the
admission policy (no host binds, refused capabilities).
Front and traefik notes
| Fact | Consequence |
|---|---|
| The front routes domains, a space routes paths (ADR-021) | A front route names space:; path and options belong to the origin's manifest. |
| traefik never loads route files written after it started | Relaunch with sklet upgrade -force on the front, never systemctl. |
| A route written before its backend is ready has empty servers | 404 until re-applied once the backend is up. |
traefik Host() has no wildcard | Declare one route per subdomain. |
The control API must not use :9080/:9443 | Those belong to the front door. |