Skip to content

Continuous deployment

nautilus new with the Kubernetes deploy feature (or --deploy) scaffolds the whole pipeline:

Dockerfile the controller image (distroless, ~binary-sized)
deploy/k8s.yaml redundant pair: RBAC, Deployment ×2, Service
.github/workflows/deploy.yml check → test → build → image → (rollout)

The chain is the pitch made concrete: a PR that fails its acceptance suites cannot merge (ci.yml); a merge that passes becomes an image tagged with its sha; the rollout step points the Deployment at that sha. Every controller running on the floor is attributable to one commit, and kubectl rollout undo is a one-command downgrade.

nautilus build already emits a self-contained controller — runtime plus project, one static binary — so the Dockerfile is three meaningful lines on distroless/static: no shell, no package manager, CA roots included for MQTT/TLS. The base has no libc, so the binary must be static: release CLIs are, and the workflow sets CGO_ENABLED=0 for its own go install.

deploy/k8s.yaml runs two replicas with the manifest’s retain: {} and redundancy: {} sections (scaffolded together): a Lease elects the scanning leader, setpoints and online edits ride a ConfigMap across restarts and failovers, and either replica answers the Service — a standby proxies API traffic to the leader. The RBAC is exactly the verbs those two objects need, delete not among them. See the Redundancy guide for how takeover works.

Kill the leader and watch the tank not care:

Terminal window
kubectl delete pod -l app=<name> --field-selector ... # or just delete the leader
# the standby's next 1s tick acquires the lease; worst case 4s

The scaffolded workflow pushes to GHCR out of the box (nothing to configure — it uses the repo’s own GITHUB_TOKEN). The final step — kubectl set image by sha + rollout status — ships commented, because it needs one secret only you can add: a namespace-scoped kubeconfig as KUBE_CONFIG. Uncomment it and the loop closes.

Rolling by sha rather than latest is deliberate: a sha pins the code, while a Deployment that says :latest redeploys to whatever was pushed most recently.

nautilus build embeds the project’s git history in the binary it emits — — N commits of program history embedded on the build line. The deployed controller then serves GET /api/program/history with no git, no .git dir, and no network: which commit it was built from, every change as a diff, and the ability to warm-swap back to any of them. The loop this page closes — commit to running controller — gets its mirror: see Program history.