Reference
Self-host
Machinaut is offered as a hosted service, but the control plane is the same image set you can run yourself. This page is about the two decisions that precede that: whether you need the control plane at all, and — if you do — how to deploy it.
A solo operator working locally needs only Lodestar OSS. The control plane earns its keep the moment a team has to govern many agents across many projects.
Do you even need the control plane?
Lodestar OSS is complete on its own for a single operator. It wraps your agent, records the chain, holds risky actions, and ships a local viewer to read all of it — on your machine, no server, no account. If that's your whole world, you don't need Machinaut.
You reach for the control plane — hosted by us, or self-hosted — when:
- A team shares the record. Sessions from every project, searchable in one place, not scattered across operators' loopback viewers.
- Someone other than the agent's author approves. Held actions need an inbox a second person can reach — the approval write-path.
- You govern a fleet. Cross-session, cross-project calibration and sentinel rollups; an org-scoped audit log; policy drafting.
The open-core boundary — what's OSS, what Machinaut adds
Two images, one host
The control plane is two Bun-runtime images behind a single Ingress host — same-origin, no CORS. The explorer image doubles as the front door: its nginx serves the SPA and proxies /v1 + /healthz to the API service, so a single port-forward to the explorer gives you a fully working app without an Ingress controller. /readyz and /metrics are deliberately not routed by the Ingress — they stay internal for kubelet probes and Prometheus scrapes.
| Service | What it is | Port |
|---|---|---|
| machinaut-api | Bun + Elysia control plane. Serves /v1, /healthz, /readyz, /metrics. | 4319 |
| machinaut-explorer | nginx serving the SPA + proxying /v1 and /healthz to the API. | 8080 |
The manifests
Deployment is Kustomize, no templating engine — plain, legible YAML. A base plus two overlays:
base/— namespace, the api + explorer Deployments / Services, Ingress, ConfigMap, a suspended prune CronJob, HPA, PDB. It is not deployable on its own — no Secret, placeholder host. Always go through an overlay.overlays/dev— adds an in-cluster Postgres, a dev Secret, a demo-seed Job, and a plain-HTTP posture.overlays/prod— production posture (MACHINAUT_ENV=production), TLS Ingress, GHCR image refs; expects an operator-created Secret and an external managed Postgres.
Building the images
Both images are Bun-runtime (argon2 + Bun.sql pin them to Bun, zero native modules). Two wrinkles:
- The control-plane image needs the unpublished
@qmilab/lodestar-*packages in its build context — supplied as a named build contextlodestarpointing at a local Lodestar checkout. - The explorer SPA is built outside Docker first (
bun run build), then packaged into nginx — TanStack Start's SPA build prerenders the shell via a local server that buildkit's sandbox won't allow, so the Vite build can't run inside the Dockerfile.
# control-plane API image (needs a local Lodestar checkout as a build context)
docker build --build-context lodestar=/path/to/lodestar -t machinaut-api .
# explorer image — build the SPA first, then package it into nginx
(cd explorer && bun install && bun run build) && docker build -t machinaut-explorer explorerImages are published only on a release tag — and need private access
ghcr.io/<owner>/machinaut-{api,explorer} only on a v* release tag (routine merges build-only). Building the control-plane image at all requires read access to the private Lodestar repo (a LODESTAR_PAT secret). Self-hosting today therefore means building from source with that access — it is not yet a public, pull-and-run image.Dev — one command
The dev overlay is fully self-contained: an in-cluster Postgres, plain-HTTP posture, and a Job that seeds a full demo chain. Signups bootstrap into org dev, so a signed-in user sees the same seeded demo as the default bearer-key view. make dev-down tears the cluster down.
make dev-up # build images → kind cluster → load → apply overlays/dev → wait
kubectl -n machinaut port-forward svc/machinaut-explorer 8080:8080
# open http://localhost:8080 → the seeded `demo` project (bearer key mk_dev_local, tenant dev)Production
Kustomize ships no Secret in prod — create it first, pointing at your managed Postgres (the in-cluster StatefulSet is dev-only):
kubectl create namespace machinaut
kubectl -n machinaut create secret generic machinaut-secrets \
--from-literal=DATABASE_URL='postgres://user:pass@your-db-host:5432/machinaut' \
--from-literal=MACHINAUT_API_KEYS='{"mk_live_xxx":{"tenant_id":"acme"}}'
# (optional: webhooks, OTLP headers — see base/secret.example.yaml. Managed approver keys
# are the planned convenience tier — the posture is customer-held; see the approval relay contract.)Then edit overlays/prod/kustomization.yaml (image newName / tag → your GHCR ref at an immutable digest) and the host in the prod overlay, and apply:
kubectl kustomize deploy/k8s/overlays/prod # review
kubectl apply -k deploy/k8s/overlays/prodProduction refuses to boot on a footgun
MACHINAUT_ENV=production makes the API refuse to start on the dev key, a non-Secure cookie, ephemeral managed keys, or a missing DATABASE_URL — so a misconfigured prod deploy fails loud instead of running insecure.Operational notes
Migrations
Run in the api initContainer (bun run migrate) on every rollout — advisory-locked and idempotent, so concurrent replicas serialize. For a CD flow that prefers a discrete pre-deploy step, apply the standalone migrate Job through its overlay (overlays/{dev,prod}/migrate), never apply -f the raw base Job.
Probes & metrics
Readiness is /readyz (a store-backed DB ping → 503 when Postgres is down, so a severed pod leaves rotation); liveness and startup use /healthz. /metrics (Prometheus) is scraped off the api Service annotations and is never exposed through the Ingress.
Retention, autoscaling, proxy
- The prune CronJob is suspended by default (retain forever). Set
MACHINAUT_EVENT_RETENTION_DAYSand flipsuspend: falseto enable. - The HPA needs metrics-server; absent it, the Deployment's replicas stand.
MACHINAUT_TRUST_PROXY=1lets the API read the ingress-setX-Forwarded-Forfor the per-source login throttle; the Ingress setsproxy-body-size: 26mto admit the 25 MiB ingest ceiling.
Where to go next