Install · Quick Start · How it works · AI · CLI · Docs
Kubernetes, from your laptop to production — one CLI, one folder convention, the same workflow everywhere.
lok8s gives you a single CLI (lo) and a single per-domain folder layout for the whole journey: spin up a throwaway cluster on your laptop, iterate with a live dev loop, and ship the exact same definitions to production. It doesn't replace the tools you already know — kustomize builds your manifests, Helm charts inflate in place, kind runs clusters locally, and Tilt powers the hot-reload loop. lok8s is the thin, opinionated orchestration around them, so clusters/<your-domain>/ means the same thing in dev, CI, and prod.
Open-sourced in 2026 after ~9 years running real production workloads — new to GitHub, but shaped by nearly a decade of operating clusters for keeps.
- 🧰 Standard tools, not a walled garden. Targets are plain
kustomize builds; Helm charts inflate via the khelm kustomize plugin (nohelmCLI to install). TheloCLI and the folder convention are orchestration and ergonomics — the artifacts underneath are vanilla Kubernetes YAML you couldkubectl applyby hand. No lock-in: lok8s produces standard manifests you can take anywhere. - 💻 Dev-first, runs against any cluster. The default experience is a local
kindcluster with TLS, a registry mirror, and a Tilt hot-reload loop that just works.lo buildemits portableartifacts.yamlyou cankubectl applyto any cluster. The Hetzner/KubeOne/CAPI provisioners are a convenience for standing up production — not a requirement. - 🤖 AI built in, local-first.
lo chatis an on-device assistant for your cluster (read-only by default, with a code-enforced safety gate), andlo mcpexposes everylocommand to agents like Claude Code over MCP. No data leaves your machine unless you explicitly opt into a frontier model. - 🧪 Battle-tested. The conventions here aren't speculative — they're the residue of ~9 years of running this in production, keeping what survived contact with reality and dropping what didn't.
- 🐚 Transparent and debuggable. The CLI is bash (via argsh) — the same
kubectl/kustomize/kindcommands you'd run by hand, just orchestrated. Nothing is hidden behind a compiled black box; you can read, lint, and step through every step. (Why bash?)
A few principles shape every decision in lok8s:
- Production is the reference; local dev is a nerfed overlay of it. You don't learn one workflow for your laptop and a different one for prod — it's the same tree, the same commands, the same
cluster.lok8s.yaml. The local cluster is just production with the expensive parts swapped out. - One cluster = one folder, keyed by FQDN. Everything about a cluster lives under
clusters/<fqdn>/. The domain is the identity, which makes multi-cluster and multi-environment setups obvious rather than clever. - Stand on standard tools. lok8s orchestrates kustomize, Helm (via khelm), kind, and Tilt — it doesn't reimplement them. If you know those, you already know most of lok8s.
- Two concerns, kept apart. Cluster creation (a pluggable driver) is separate from cluster content (kustomize targets). Swapping how a cluster is born never touches what runs on it.
- Minimal magic, maximal transparency. Rendered artifacts are plain YAML, the CLI is readable bash, and the only ordering primitive is an explicit
spec.bootstraplist. When something breaks, you can see exactly what ran.
Honesty up front — lok8s is opinionated, and that won't fit everyone.
Reach for lok8s when you want to…
- Have the same workflow from local kind to production instead of maintaining two parallel setups.
- Manage one or many clusters with a clear, FQDN-keyed folder convention.
- Keep a fast local dev loop (Tilt hot-reload, local registry, working TLS) without bespoke glue.
- Use kustomize + Helm charts as your manifest layer and want ergonomics around them.
- Provision on Hetzner (kind/KubeOne/CAPI) with sensible, batteries-included defaults — or just deploy to a cluster you already have.
Look elsewhere (or use lok8s only for the deploy side) when…
- You're fully invested in a managed platform's native workflow (EKS/GKE/AKS + their tooling) and don't want another convention on top.
- You prefer a pure-GitOps, controller-driven model (Argo/Flux as the source of truth) — lok8s can emit manifests for that, but its dev-loop is CLI/Tilt-centric, and the in-tree
lo gitopslayer is still being built. - You need turnkey production provisioning on a cloud other than Hetzner today — the provisioning drivers currently target Hetzner. (You can still deploy lok8s-built artifacts to any cluster; you'd just bring your own provisioning.)
- A compiled, single-binary tool with no bash anywhere is a hard requirement for your team.
One command bootstraps a lok8s project in the current directory — it installs
b (the environment manager) if missing, pulls the framework plus your
profile's pinned toolchain into .bin/, and drops a re-runnable lo-up:
curl -fsSL https://get.lok8s.io | shIt prompts when a terminal is attached and runs unattended otherwise:
curl -fsSL https://get.lok8s.io | sh -s -- -y # no prompts (CI)
curl -fsSL https://get.lok8s.io | sh -s -- -p kubeone -y # a specific profileRather inspect before running? Good instinct.
curl -fsSL https://get.lok8s.io -o lo-up, read it, thensh lo-up. The script is self-contained (the argsh runtime is bundled) and also published at lok8s.io/lo-up.
Other ways to install (drive b yourself, or use mise)
# Drive b directly
curl -fsSL https://get.binary.help | sh # install b
b env add github.com/kernpilot/lok8s#local # add a profile (local dev)
b install # pull it into the projectPrefer mise? A mise.toml ships at the repo root —
mise install && mise activate provisions the same toolchain. Then lo doctor to verify.
Cloning the repo directly? The argsh runtime is vendored in .bin/, so
lo doctor runs immediately and tells you which tools are still missing — no
b install needed just to diagnose the environment.
Profiles — each ships only the binaries it needs:
| Profile | Adds | Use case |
|---|---|---|
core |
framework only | Remote deploy only — no kind/Tilt |
kustomize |
kustomize plugins | Standalone kustomize plugin use |
local |
kind, Tilt, mkcert | Local dev (recommended starting point) |
capi |
clusterctl, hcloud |
Cluster API provisioning |
kubeone |
kubeone, hcloud |
KubeOne provisioning |
Every profile ships a preconfigured clusters/lok8s.dev/ — a local cluster with working TLS out of the box. Bring your own FQDN later, or use *.[N].lok8s.dev for multiple projects.
Prerequisites: Docker, and bash ≥ 4.3 (macOS ships 3.2 — brew install bash). Everything else comes from b (or mise). Run lo doctor to check.
From zero to a running local cluster with a live dev loop:
lo use lok8s.dev # select the active domain (ships preconfigured)
lo up # create the kind cluster, bootstrap infra, start Tilt
# → Tilt UI on the URL it prints (per-domain port, 10351–10499)
lo status # Running ✓
lo down # tear it all down when you're doneThat's the interactive loop. For headless/CI or deploying to a remote cluster, the same definitions drive a build → deploy pipeline:
lo build # render every kustomize target → artifacts/<target>/artifacts.yaml
lo deploy # apply built artifacts (CRDs first, then resources, with health waits)
lo lint # validate specs, bootstrap entries, and target referenceslo build's output is plain Kubernetes YAML — kubectl apply -f clusters/<domain>/artifacts/<target>/artifacts.yaml works against any cluster, with or without the rest of lok8s.
lok8s keeps two concerns strictly separate:
- Cluster creation — how the cluster comes to exist. Handled by a driver (
.lok8s/drivers/<kind>/main), selected by thekind:of yourcluster.lok8s.yaml. - Cluster content — what runs on it. Plain kustomize, split into two planes: ordered bootstrap infrastructure and independent workload targets.
Everything is keyed by FQDN. A cluster domain owns a cluster (cluster.lok8s.yaml); a deployment domain ships content to another domain's cluster (deploy.lok8s.yaml).
flowchart LR
spec["cluster.lok8s.yaml<br/>kind: Lo · KubeOne · Capi · Kkp"] --> drv{{driver}}
drv -->|provision| k8s[("Kubernetes cluster")]
k8s --> a["Plane A — bootstrap<br/>spec.bootstrap addons<br/>(CNI → LB → cert-manager …)"]
a --> b["Plane B — workloads<br/>targets/* · kustomize"]
b -->|"Tilt (dev) / lo deploy (CI)"| run(["running cluster"])
Concretely, lo up runs:
lo up # domain comes from `lo use` / --domain
├─ provision driver creates the cluster (kind / KubeOne / CAPI / KKP)
├─ bootstrap framework applies spec.bootstrap (CNI → MetalLB → cert-manager → …,
│ addons in order, waits healthy health-gated between stages)
└─ tilt up Tilt builds & live-reloads (or: lo build + lo deploy, headless)
The two content planes:
- Plane A — bootstrap (cluster infrastructure). An ordered
spec.bootstraplist of framework addons (CNI, load balancer, cert-manager, …) applied at provision time, with health waits between stages. The cluster isn't "ready" until this finishes. - Plane B — workloads. User-named kustomize directories under
targets/, each built independently into its ownartifacts.yaml. No framework-level ordering — you express any ordering you need with Tilt'sresource_depsor your GitOps engine.
Drivers are pluggable cluster backends:
| Kind | Runtime | Use for |
|---|---|---|
Lo |
Docker + kind | Local dev / CI |
KubeOne |
KubeOne (Hetzner) | Self-managed production |
Capi |
Cluster API (Hetzner / CAPH today) | Declarative production provisioning |
Kkp |
Kubermatic KKP | Hosted control planes |
The four drivers share one
.lok8s/tree and one spec format.Lo(local) needs nothing but Docker; the production drivers target Hetzner today but are optional — you can run lok8s entirely against clusters you provision yourself. Adding a driver for another backend is a documented extension point (see Extensibility).
For the full model — layer map, atoms/molecules, build/deploy pipeline — see ARCHITECTURE.md and the Concepts guide.
This single convention is what makes the "same workflow everywhere" promise hold. The framework (/.lok8s/) stays flat and framework-owned; your content lives in a parallel clusters/ tree, one directory per cluster, named by its FQDN:
clusters/
├── lok8s.dev/ # local dev cluster (ships with lok8s)
│ ├── cluster.lok8s.yaml # the spec — kind, bootstrap, network, …
│ ├── targets/ # workload plane: one kustomize dir per target
│ │ ├── platform/
│ │ └── apps/
│ ├── artifacts/ # rendered output (gitignored, rebuilt on demand)
│ └── secrets/ # per-domain secret store (encrypted, opt-in)
├── cluster.example.in.net/ # production cluster (same structure!)
│ └── cluster.lok8s.yaml
└── api.example.com/ # deployment domain → deploys onto another cluster
└── deploy.lok8s.yaml
Why this is powerful:
- The folder name is the cluster's API hostname, so the same brand can be served by different environments without collisions (
clusters/example.com/locally,clusters/cluster.example.in.net/in prod). - Secrets are per-domain (
clusters/<domain>/secrets/), so dev and prod can never accidentally cross-pollinate credentials. - A
Deploydomain carries no cluster of its own — itclusterRefs another domain and ships workloads there, which is how one repo can target many clusters. - It's just folders and YAML — diff-able, reviewable, and obvious to a newcomer.
See ARCHITECTURE.md for the complete tree and Specs reference for every field.
The local experience is the part lok8s polishes hardest, because it's where you live day to day:
- One command, full loop.
lo upcreates akindcluster, applies yourspec.bootstrapinfrastructure, and starts Tilt — which reads yourservices.yaml, builds images, wiresdocker_build+live_update, and gives you a UI at the URL it prints (a per-domain port in10351–10499, so parallel projects never collide). Edit code → Tilt syncs and reloads. (Local Dev guide) - Working TLS, no manual cert juggling. The
secrets.lok8s.devkustomize plugin'scert:generator mints leaf certificates from a shared local dev CA — nomkcertdance per project.lo trustadds the CA to your system store so browsers are happy. (Secrets guide, Kustomize plugins) - Fast, shared registry mirrors. A pull-through mirror network is shared across all your lok8s projects, so images are pulled once, not once-per-cluster. Opt out per project if you'd rather not. (Shared Registries guide)
- Multi-project friendly. Use
*.[N].lok8s.devslots to run several local clusters on isolated Docker networks side by side.
Define services once, in a committed services.yaml, with personal overrides in a gitignored services.<config>.yaml ("I'm not working on the frontend today" → enabled: false). Each buildable service carries a small lok8s.yaml describing its build, ports, and live-update rules. (Services guide)
Two honest paths to production:
- Provision with lok8s (Hetzner today). The
KubeOneandCapidrivers stand up real clusters on Hetzner Cloud (and bare metal via Hetzner Robot), with batteries-included networking, CNI, encryption-at-rest, and backups guidance. (CAPI · Bare Metal · Networking · Security · Backups) - Bring your own cluster.
lo buildrenders standardartifacts.yaml— plain manifests youkubectl applyto whatever cluster yourKUBECONFIGpoints at. EKS, GKE, a Raspberry Pi, a colleague's kind cluster — ifkubectlcan reach it, lok8s' output runs on it. (lo deployautomates the apply against the kubeconfig it resolves for the domain.)
The optional operator (shell-operator-based) reconciles Lo and Capi CRDs on a management cluster using the same bash libraries as the CLI, so cluster lifecycle can be declarative when you want it. (Operator guide)
The hosted, managed-platform layer (kubehz) is a separate product built on top of lok8s — lok8s works fully without it. No-lock-in is a design goal, not a slogan.
lok8s treats AI as a first-class, local-first capability — not a cloud dependency.
lo chat— an on-device cluster assistant. Ask "why won't this deploy?" or "what's the LB IP?" and it routes throughlotools, gathers facts, and streams a markdown answer in your terminal. It runs read-only by default, enforced in code (not by trusting the model), so it can't mutate your cluster unless you switch posture with/posture open. Backends are local: Ollama or any OpenAI-compatible server (llama-server, llamafile, vLLM). Frontier CLIs (claude/gemini/codex) are strictly opt-in handoffs. Runlo chat --checkfor a guided setup. (Local AI guide)lo mcp— your CLI as agent tools. Every leaflocommand is exposed as an MCP tool (lo_status,lo_build,lo_deploy, …) over stdio, so agents like Claude Code or Cursor can drive lok8s the same way you do. Commands are tagged@readonly/@idempotent/@destructive, and a deterministic posture gate decides what an agent may actually run. A ready-to-use.mcp.jsonships in the repo root.lo ai— wire skills into your assistant. The repo ships curated skills (cluster specs, services, addons, secrets, the dev loop, troubleshooting…).lo ai link claudesymlinks them into.claude/skills/for native loading; other agents get them by injection.lo ai checkreports the whole setup at a glance.
Try it in two commands:
lo chat --check # guided: checks the bridge + a local model, prints setup hints
lo chat # then ask, e.g. "why won't my deployment start?"If a piece needs setup (e.g. the MCP bridge wants argsh builtins install), lo ai check / lo doctor tell you exactly what to run.
lok8s is conventions, not a cage — every layer has a documented seam:
- Custom cluster drivers. A driver is a bash file at
.lok8s/drivers/<kind>/mainimplementing four functions —driver::provision,driver::destroy,driver::status,driver::kubeconfig(plus an optionaldriver::post_provision). Setkind: <YourKind>in the spec and lok8s dispatches to it. The Driver Contract reference includes a complete worked example (a k3s driver). - Your own addons. Drop a kustomize-buildable directory at
.lok8s/addons/<name>/(a khelmChartRenderer+ layeredvalues.<driver>.yaml/values.<provider>.yaml, or any plain kustomization) and reference it by name inspec.bootstrap. (Addons guide) - Adopt what you already have. Point a target's
kustomization.yamlat an existing kustomize base (resources: [ ../path/to/your/kustomization ]), or inflate an existing Helm chart via a khelmChartRenderer— no rewrite required. - Add tools. The toolchain is managed by
b: add an entry to.bin/b.yaml, assign it a profile group,b install, and it's onPATH.
It's a fair question, so here's the honest answer. The lo CLI is bash, built on argsh.
- Bash is the native glue for Kubernetes tooling. lok8s' job is to orchestrate
kubectl,kustomize,kind,clusterctl,kubeone, and friends — which are themselves CLIs. Bash calls them directly, with no SDK drift or version-matrix games. What lok8s runs is what you'd run by hand. - Transparency and debuggability. There's no compiled binary hiding the logic.
lo --verboseshows every command; you can read the libraries under.lok8s/, setset -x, and step through a provision. When production breaks at 2am, "it's just shell" is a feature. - argsh makes the bash sane. argsh adds typed argument parsing, generated
--help, subcommand dispatch, and structure — so the code reads like a real CLI, not a pile ofgetopts. It'sshellcheck-clean in CI (no new warnings allowed), and the same argsh metadata that powers--helpis what generates the MCP tool schema for free.
The kustomize plugins and the lo chat engine, where a typed/compiled language genuinely fits, are written in Go. Bash is used where it's the right tool, not everywhere.
| Command | Description |
|---|---|
lo use [domain] |
Set / show the active domain |
lo up [--open-tilt] |
Provision cluster + bootstrap + start Tilt |
lo down |
Stop Tilt + delete the cluster |
lo status |
Cluster health + per-target build state |
lo provision |
Full lifecycle: create + bootstrap + build + deploy |
lo build [target…] |
Render kustomize targets → artifacts/ |
lo deploy [--filter k=v] [target…] |
Apply built artifacts (CRDs → resources → health) |
lo lint |
Validate specs, bootstrap entries, target refs |
lo doctor |
Diagnose the local environment / toolchain |
lo addons [name] |
List / inspect framework bootstrap addons |
lo kubeconfig |
Print the domain's kubeconfig (--oidc for the kubelogin exec-plugin) |
lo destroy |
Tear down a cluster |
lo clean [--all] |
Clean volumes; optionally prune Docker |
lo chat |
Local AI assistant (read-only by default) |
lo ai check|skills|link|unlink |
Manage AI skills + integration |
lo mcp |
Start the MCP tool server (stdio) |
lo tilt up|down|status|restart |
Manage the Tilt environment |
lo registry up|down|status|clean |
Manage registry mirrors |
Most commands act on the active domain (set by lo use) or an explicit --domain <domain> — [target…] and [name] are the only positionals. Global flags: --verbose|-v, --force|-f, --remote|-r, --cluster|-s, --kubernetes, --config, --domain, --domain-sans. Full reference: docs/reference/cli.md.
Full documentation lives at lok8s.io. Good entry points:
- Getting Started · Concepts · Architecture
- Workflows: Local Dev · Services · Secrets · Deploying · Local AI
- Production: CAPI · Bare Metal · Networking · Operator
- Reference: CLI · Specs · Driver Contract · Kustomize Plugins
npm run docs:dev # local docs site
npm run docs:build # build
Contributions are welcome — see CONTRIBUTING.md and the agent/contributor guide in AGENTS.md. In short: conventional commits, keep CI green (npm run lint + npm test), and security is paramount — never pipe untrusted remote content into a shell, never commit secrets, validate external input.
lok8s is built on — and shares a philosophy with — a few sibling tools:
b· binary.help — your one-stop binary manager. It installs and pins lok8s' toolchain, and lok8s ships as abprofile.argsh· arg.sh — the framework theloCLI is built on; it brings structure and maintainability to complex Bash (typed args, dispatch, and the metadata that becomes lok8s'--helpand MCP tool schema).atty· atty.sh — a suckless-style PTY proxy (Zig) that drops an LLM exec dialog, atuin autosuggest, and guardrail confirmations between your terminal and your shell.