The Operator
The lok8s operator runs on a management cluster and watches lok8s custom resources. It uses shell-operator with bash hooks that reuse the same library code as the CLI.
Alpha
The Lo lifecycle is complete — creation, drift detection, kubeconfig publication, and finalizer-guarded teardown. Capi covers creation and status sync only: deleting a Capi resource does not tear down the cluster (#6). Don't point the Capi path at production credentials yet.
Architecture
CLI mode (lo) Operator mode (shell-operator)
synchronous event-driven
runs from disk watches CRDs
one-shot reconciliation loop
\ /
\ /
Shared bash libraries
.lok8s/libs/*
.lok8s/drivers/*/mainThe operator container bundles the same libraries and driver contracts as the CLI. Hooks source the libraries with an import() { :; } shim (since import is an argsh builtin that doesn't exist in plain bash).
Custom Resource Definitions
The operator defines its CRDs in the cluster.lok8s.dev API group:
Lo (cluster.lok8s.dev/v1beta1)
For local/CI clusters:
# Minimal form — the framework derives everything from the domain
# (slot 125) and domain-independent defaults. See the Specs reference
# for the full defaulting table.
apiVersion: cluster.lok8s.dev/v1beta1
kind: Lo
metadata:
name: local
namespace: default
spec:
cluster:
domain: lok8s.dev
bootstrap:
- cilium
- metallb # opt in; default is [cilium] onlykubectl get lo
# NAME PHASE READY DOMAIN AGE
# local Provisioned true lok8s.dev 5mCapi (cluster.lok8s.dev/v1beta1)
For production clusters via Cluster API:
apiVersion: cluster.lok8s.dev/v1beta1
kind: Capi
metadata:
name: prod
spec:
kubernetes:
version: "v1.31.10"
cluster:
domain: prod.example.com
managementCluster:
domain: mgmt.example.com
hcloud:
region: fsn1
sshKeyName: my-key
controlPlane:
replicas: 3
workers:
general:
replicas: 3
type: cax21
gitops:
provider: flux
repo: https://github.com/myorg/infra.gitkubectl get capi
# NAME PHASE READY DOMAIN PROVIDER AGE
# prod Provisioned true prod.example.com hetzner 10mDeploy (cluster.lok8s.dev/v1beta1)
For deployment domains that target an existing cluster:
apiVersion: cluster.lok8s.dev/v1beta1
kind: Deploy
metadata:
name: api
spec:
clusterRef:
domain: prod.example.comNote: Deploy CRD workload selection is being reworked — target selection will land alongside the
services.yamltargets-map redesign. For now, a Deploy spec only carriesclusterRef.
kubectl get deploys.cluster.lok8s.dev
# NAME PHASE CLUSTER AGE
# api Deployed prod.example.com 3mHooks
lo-reconcile.sh
Full lifecycle for Lo resources — the same driver contract as the CLI:
- Create/Modify: adds a
lok8s.dev/lo-teardownfinalizer, checksdriver::statusfirst (idempotent — aRunningcluster is never re-provisioned), thendriver::provision+ framework bootstrap (spec.bootstrapaddons), publishes the kubeconfig as Secret<name>-kubeconfig, and setsstatus.kubeconfig.secretRef. - Delete: the finalizer holds the object while
driver::destroytears the cluster down; on success the finalizer is removed and the kubeconfig Secret deleted. A failed teardown keeps the finalizer and is retried. - Drift: a
*/3schedule re-lists everyLoand converges — a cluster deleted out-of-band is re-provisioned, a finished manual teardown is detected.
capi-reconcile.sh
Watches Capi resources. On Added/Modified events:
- Updates status to
Provisioning - Detects the CAPI provider from the spec
- Generates CAPI resources from templates
- Applies resources to the cluster
- Status sync is handled by
capi-status-sync.sh
capi-status-sync.sh
Watches cluster.x-k8s.io/v1beta1 Cluster resources with the lok8s.dev/managed: "true" label. When a CAPI Cluster's status changes:
- Maps CAPI phase to lok8s phase
- Updates the corresponding Capi CR status
- On
Provisioned: extracts kubeconfig, bootstraps GitOps or runs direct deploy
Installation
# Apply CRDs
kubectl apply -f operator/crds/
# Deploy the operator (Capi reconciliation only)
kubectl apply -k operator/deploy/
# OR: deploy with the Lo driver enabled (kind clusters)
kubectl apply -k operator/deploy/lo/The operator runs in the lok8s-system namespace with a dedicated service account and RBAC rules.
The lo/ overlay mounts the node's Docker socket, enables host networking (kind kubeconfigs point at host ports), and runs as root — a deliberate trade-off for CI and single-node management hosts. Do not apply it on multi-tenant clusters.
Container Image
The operator image is built from the project root:
docker build -t ghcr.io/kernpilot/lok8s-operator:0.1.0 -f operator/Dockerfile .The Dockerfile:
- Starts from
ghcr.io/flant/shell-operator:v1.14.0 - Installs kubectl, kustomize, yq, jq, docker-cli, kind, clusterctl, flux, git, openssh-client, gettext (for
envsubst), and the khelm kustomize plugin (bootstrap addon charts) - Copies hooks, driver contracts, libraries, bootstrap addons, CAPI templates, and CRDs
- Strips exec bits from the library trees (shell-operator treats every executable under
/hooksas a hook)