The lo CLI is an argsh script located at .lok8s/lo.
lo up runs provision → framework bootstrap (applies spec.bootstrap addons via .lok8s/libs/bootstrap) → Tilt. lo build renders the domain kustomization into one artifacts.yaml; lo deploy applies that single artifact (CRDs first, then the rest). lo lint validates spec.bootstrap entries and target kustomizations. See Concepts and Specs reference for the model.
| Flag | Short | Description |
|---|---|---|
--verbose |
-v |
Enable verbose/debug logging (sets DEBUG=1) |
--force |
-f |
Force operation without prompts (also recreates immutable/terminating conflicts, like --force-recreate) |
--force-recreate |
On apply, recreate objects blocked by an immutable field or a stuck Terminating finalizer | |
--remote |
-r |
Provision on a remote VM (activates spec.provider + spec.remote) |
--kubernetes |
Kubernetes version to use | |
--cluster |
-s |
Cluster name to manage (default: local) |
--config |
Kind config file path | |
--domain |
Domain name override | |
--domain-sans |
Domain SANs override |
Start a cluster with Tilt.
lo up [--open-tilt|-o] [--remote|-r]If clusters/<domain>/cluster.lok8s.yaml exists, uses the provision dispatch system. Otherwise falls back to legacy direct kind/registry calls.
Steps: provision cluster, apply spec.bootstrap addons in order via the framework bootstrap (.lok8s/libs/bootstrap), start Tilt.
With --remote: provisions a VM via spec.provider, then runs kind on the remote Docker host. See Remote clusters below.
| Flag | Description |
|---|---|
--open-tilt, -o |
Open the Tilt UI in a browser after startup |
Stop the cluster and Tilt.
lo downStops Tilt and deletes the kind cluster. Registries are handled by their sharing
mode: a non-shared setup (the default) is project-local with nothing to
reuse, so its registry containers are torn down (the named volumes — and thus
the build cache — are kept); a shared setup (opt-in) is left running — the
pull-through mirrors are reused across clusters and a warm build/cache
speeds up the next lo up (remove them with lo registry down, or
lo registry clean --shared to drop volumes too).
Clean up local volumes and optionally prune Docker.
lo clean [--all|-a]Stops Tilt, deletes the kind cluster, removes cluster-prefixed Docker volumes, and cleans registries.
| Flag | Description |
|---|---|
--all, -a |
Also run docker system prune -f |
Provision a cluster through the full lifecycle.
lo provision [--domain <domain>] [--bootstrap|-b] [--force|-f] [--remote|-r]Resolves the cluster spec, sources the driver contract, calls driver::provision, then runs bootstrap::apply to apply spec.bootstrap addons in order with health waits between stages.
With --remote: loads spec.provider, provisions the cloud VM, then either sets DOCKER_HOST to the remote Docker (docker mode) or syncs the repo and runs lo provision on the VM (CI mode). See Remote clusters.
| Flag | Description |
|---|---|
--bootstrap, -b |
Re-apply spec.bootstrap only, on an existing cluster (skip the infra reconcile) |
--force, -f |
Global flag: skip the real-infrastructure confirmation prompt (cloud drivers) |
Apply or re-apply bootstrap addons without full re-provisioning.
lo bootstrap [--domain <domain>]Reads spec.bootstrap from the cluster spec and applies each addon in order. Useful after changing bootstrap entries or updating addon values. Addons resolve to .lok8s/addons/<name>/ (framework) or clusters/<domain>/targets/<path> (cluster-specific). See Bootstrap Addons.
Render the domain kustomization into one artifact.
lo build [--domain <domain>] [--cluster-override <domain>]Runs kustomize build --enable-alpha-plugins on the domain's own kustomization (clusters/<domain>/kustomization.yaml) and writes ONE clusters/<domain>/artifacts.yaml. The domain kustomization composes the targets it wants, in order — local and shared:
# clusters/<domain>/kustomization.yaml
resources:
- ./targets/networking # domain-local target
- ../../.targets/monitoring # shared targetA referenced target that does not exist is a clear kustomize build error. There is no per-target loop and no artifacts/<target>/ output — target selection and ordering live in the kustomization you author.
Deploy the domain artifact to a cluster.
lo deploy [--domain <domain>] [--cluster-override <domain>] [-l|--label key=value]Applies the single clusters/<domain>/artifacts.yaml: CRDs first (server-side apply + wait for Established), then the rest (server-side apply + a scoped wait for the manifest's own workloads to become ready). Run lo build first.
Selective deploy is opt-in: pass -l key=value to apply only the objects carrying that label — e.g. lo deploy -l lok8s.dev/name=zitadel. It requires you to have labelled the targets you want to address (kustomize labels: or metadata.labels); without a match it is a graceful no-op. The key may be a bare key or a namespaced one (lok8s.dev/name).
| Flag | Description |
|---|---|
-l, --label |
Only deploy objects carrying this key=value label (opt-in selective deploy) |
--cluster-override |
Override the cluster domain used for kubeconfig resolution (deploy domains) |
Destroy a cluster.
lo destroy [--domain <domain>]Calls driver::destroy from the appropriate driver contract.
Rebuild a cluster from bare metal (disaster recovery). Orchestrates
resolve → doctor → consent → rebuild → provision → verify, reusing the provider's
provider::rebuild node reset and a fresh lo provision (incl. the bare-metal
#wipe-devices wipe). Requires a cluster domain whose provider implements
provider::rebuild. Restores the cluster, not application data — see
Disaster Recovery and Backups.
lo recover <domain> # full recovery (prompts once to confirm)
lo recover <domain> --dry-run # preview the rebuild plan; change nothing
lo recover <domain> --skip-rebuild # re-provision + verify only
lo recover <domain> --force # skip the confirmation promptName the target: the positional <domain> is the documented form and outranks --domain and the active domain — a command that reimages a fleet must not inherit whatever .active points at mid-incident. Omitting both falls back to --domain, then the active domain.
| Flag | Description |
|---|---|
--dry-run |
Run doctor + the provider::rebuild plan under CLOUD_DRY_RUN (reimages nothing), then stop before provision. |
--skip-rebuild |
Skip the node rebuild — run lo provision + verify only. |
--force, -f |
Global flag: skip the destructive-consent prompt (also honored via LOK8S_NONINTERACTIVE=1). |
The destructive-consent prompt is the guard and lives in the command;
provider::doctor only advises, and provider::rebuild enforces via its own
atomic preflight. --dry-run is genuinely safe (it reimages nothing).
Scaffold lok8s config from a correct template, so nothing is hand-written from imagination.
lo init service <name> [--path <dir>] [--force]
lo init test [--path <dir>] [--force]lo init service <name> scaffolds a bare per-service lok8s.yaml (shaped to pass the per-service validator), registers it in the project-root services.yaml, and ensures the project Tiltfile is the canonical 2-line loader.
lo init test scaffolds a domain-parameterized Playwright integration suite into tests/ (default; override with --path). The generated suite is project- and domain-agnostic: it runs the SAME specs against your dev cluster, staging, and production by changing only LOK8S_TEST_DOMAIN. See Testing. It refuses to overwrite a non-empty directory unless --force (and even then copies file-by-file, preserving local additions).
| Flag | Description |
|---|---|
--path, -p |
Target directory (service dir / tests/ dir) |
--force, -f |
Overwrite existing files / non-empty target |
Set or show the active domain.
lo use [domain]Without arguments: shows the active domain and lists all available domains with their kind types. With a domain argument: validates the domain directory exists and writes it to clusters/.active.
Validate domain structure and specs.
lo lint [--domain <domain>]Checks:
- Each domain has
cluster.lok8s.yamlordeploy.lok8s.yaml - Each
spec.bootstrapentry resolves to an existing driver addon directory or user path - Kustomization files under
targets/reference existing resources - Secrets: committed encrypted (
.encpresent and current), and no per-domain secret is shadowed in the deprecated flat.secrets/store (identical copy = stale duplicate; differing copy = active drift)
Check cluster health and status.
lo status [--domain <domain>]Delegates to the driver contract's driver::status function. For Lo clusters: checks if the kind cluster exists. For Capi clusters: queries the CAPI Cluster resource phase.
GitOps integration (Flux / Argo). Deferred. Both subcommands currently return a deferred-error stub — the integration is being redesigned around the new services.yaml targets-map model.
lo gitops flux [--domain <domain>] # (deferred)
lo gitops argo [--domain <domain>] # (deferred)The kind cluster is managed by the lifecycle commands — there is no lo kind
command. Use lo up / lo down / lo clean (create + teardown), lo provision
/ lo destroy (provision without starting Tilt), and lo kubeconfig (extract
the kubeconfig). The Docker bridge network is created automatically from
spec.network.
Manage the Tilt environment.
lo tilt up # Start Tilt in background
lo tilt down # Stop Tilt
lo tilt status # Run tilt doctor
lo tilt restart # Stop + startManage Docker registry mirrors.
lo registry up # start the mirrors for the active domain
lo registry down
lo registry status [--shared|-S]
lo registry clean [--shared|-S] # --shared also clears the shared mirror networkRegistries are derived entirely from spec.registries (the mirrors[] plus the framework-private build and cache registries) — there are no per-registry flags; the only flag is --shared/-S, which includes the shared lok8s-registries network for status/clean. Registries run on the configured Docker bridge network (default: lok8s at 10.125.125.0/24 for slot 125); IPs are computed automatically by the driver from spec.network.cidr and spec.registries.shared.network.cidr. See Specs reference.
The default 6-registry set:
| Name | Default IP | Hostname | Purpose |
|---|---|---|---|
build |
10.125.125.101 |
lok8s.local |
Tilt push target for locally-built images |
cache |
10.125.125.102 |
lok8s.cache |
Pre-pull target for build:false services with a remote registry |
io-docker |
10.125.125.103 |
docker.io |
Pull-through mirror |
io-quay |
10.125.125.104 |
quay.io |
Pull-through mirror |
io-k8s |
10.125.125.105 |
registry.k8s.io |
Pull-through mirror |
io-ghcr |
10.125.125.106 |
ghcr.io |
Pull-through mirror |
build and cache always live on the project subnet (.101/.102),
and by default the mirrors do too (.103+). With
spec.registries.shared.enabled: true (opt-in) the mirrors move to the
shared lok8s-registries network (10.125.200.2+) so multiple projects
reuse one cache — see
Shared registries for the trade-off.
Manage the local cache registry — pre-pull private/CI images so kind can fetch them without upstream credentials.
lo image cache <service> [--force|-f] # Pre-pull a single service's image
lo image cache --all [--force|-f] # Drain the pre-pull queue written at build time
lo image list # Show what's currently in the cache registry
lo image clean # Drop the cache registry volumeThe cache flow runs automatically as part of lo build / lo up when any service has build: false and a resolved registry.endpoint. See Services Configuration → Cache mode for the full pipeline. Parallelism is controlled via registry.parallel in services.yaml (0 unlimited, 1 sequential default, N≥2 bounded).
Manage the secret cache ($PATH_SECRETS) and its optional SOPS/age encryption
(lo secrets list / print inspect it). See the Secrets guide
for the full workflow.
lo secrets init # set up SOPS/age from your SSH key
lo secrets set --name <n> --namespace <ns> <key> [value] # write a value (omitted: tty prompt / piped stdin; `-`: stdin, needs argsh with arg-sh/argsh#176)
lo secrets set --name <n> <key> --encrypt # write + SOPS-encrypt this one file (-e/--enc; needs .sops.yaml)
lo secrets allow # approve bash: generators after a change
lo secrets encrypt # write committable Secret.*.enc files
lo secrets decrypt # restore the plaintext cache from .enc
lo secrets add-key <ssh-pubkey-path|age1…> # add an age recipient to .sops.yaml + re-key the store (--all: every domain)
lo secrets env --name <n> [--namespace <ns>] # emit injection-safe `export KEY=value` lines for a cached secret (for eval)
lo secrets list | print [pattern...] | path # inspect the cacheEncryption: init derives an age recipient from ~/.ssh/id_ed25519 (via
ssh-to-age, ed25519 only) and writes .sops.yaml; encrypt/decrypt
round-trip the cache so secrets commit safely as Secret.*.enc. Needs sops
and ssh-to-age (b install).
Start an MCP (Model Context Protocol) tool server over stdio.
lo mcpExposes every user-facing leaf lo subcommand as a callable tool via the MCP protocol. AI clients (Claude Code, VS Code Copilot, Cursor) connect over stdio and can invoke up, down, build, deploy, status, and all other commands programmatically. Dispatchers (tilt, gitops, kubehz, …) are traversed but not exposed -- only their leaf commands appear as tools. Framework-internal commands (hidden from --help) are not exposed either.
Commands carry tool annotations that inform the client about behavior:
| Annotation | MCP hint | Effect |
|---|---|---|
@readonly |
readOnlyHint: true |
Client may auto-run without confirmation |
@destructive |
destructiveHint: true |
Client shows confirmation dialog |
@idempotent |
idempotentHint: true |
Client knows retries are safe |
Requires the argsh native builtin (argsh.so). Install it with:
argsh builtins installThe .so must be discoverable via one of: ARGSH_BUILTIN_PATH, PATH_BIN/argsh.so, BASH_LOADABLES_PATH, or LD_LIBRARY_PATH.
Configure your AI client using the .mcp.json included in the project root.
Print a domain's kubeconfig on stdout.
lo kubeconfig [--domain <domain>] # admin kubeconfig (alias: lo kc)
lo kubeconfig --oidc # kubelogin exec-plugin kubeconfig (browser OIDC login)
lo kubeconfig --cluster-override <domain> # resolve against another cluster domainA deploy domain follows its spec.clusterRef to the real cluster. The --oidc form reuses the same server + CA as the admin kubeconfig but authenticates the user through spec.oidc's IdP via kubectl oidc-login — safe to hand to teammates.
Static security-posture audit — read-only and cluster-free.
lo audit [domain] [--json]Scans the domain's specs, secrets hygiene, and rendered targets for posture findings. --json emits machine-readable output for CI.
Diagnose the local environment and toolchain.
lo doctorChecks required binaries, versions, Docker/kind state, and common misconfigurations, with a fix hint per finding.
Install the local dev CA into the OS/browser trust stores (wraps mkcert -install, same CAROOT the cert: secrets generator uses).
lo trustPrint lok8s and toolchain versions.
lo versionList driver bootstrap addons for the active cluster; name one to inspect it.
lo addons [name]Driver-specific commands.
lo drivers --list # list available drivers
lo drivers <name> <args…> # invoke a driver's own subcommandskubehz platform integration (alias: lo kh). Requires spec.kubehz in the cluster spec.
lo kubehz register # register the cluster with the platform
lo kubehz deregister # remove it
lo kubehz status # registration + heartbeat status
lo kubehz claim-code # print the one-time claim code for the dashboard
lo kubehz claim --nonce <v> # place a dashboard-minted claim nonce for the agent to echo
lo kubehz re-enroll # re-enroll a regenerated agent token (heartbeats resume)
lo kubehz join # mint a node join ticket (hosting: shared)
lo kubehz assess # platform assessment + handover feasibility
lo kubehz handover # control-plane handover (receive/preseed on the eject target)Manage the Go kustomize plugins (alias: lo ku).
lo kustomize build # compile plugin binaries into the plugin home
lo kustomize test # plugin unit + integration tests
lo kustomize list # list discoverable plugins
lo kustomize clean # remove built binariesChat with a local AI assistant (transparent, streaming; read-only by default).
lo chatManage the AI integration behind lo chat and the agent skills.
lo ai check # check the AI setup (runtime + skills)
lo ai skills # list skills + per-assistant delivery
lo ai link # link skills into an assistant skill dir
lo ai unlink # remove linked skillsThe --remote flag enables provisioning Lo clusters on remote VMs
instead of the local Docker host. It requires spec.provider and
optionally spec.remote in the cluster spec.
Docker mode (default): The local machine orchestrates everything —
kind, registries, bootstrap — but Docker commands target the remote VM
via DOCKER_HOST=ssh://<ip>. The API is accessed through an SSH tunnel.
lo up --remote --domain my.lok8s.devCI mode (spec.remote.mode: ci): The repo is rsynced to the VM and
lo provision runs entirely on the remote. The local machine only
triggers the process and sets up an SSH tunnel for kubectl access.
lo up --remote --domain ci.lok8s.dev # spec.remote.mode: ci--remotecauseslibs/provisionto loadspec.provider(e.g. Hetzner)- The provider creates the VM (with cloud-init for Docker, SSH config, etc.)
- The Lo driver waits for SSH, cloud-init, and Docker to be ready
- Docker mode: sets
DOCKER_HOST=ssh://root@<ip>, runs kind locally - CI mode: rsyncs the repo, runs
lo provisionon the VM via SSH, optionally starts Tilt, sets up nginx expose + kubeconfig tunnel - In CI mode,
driver::provisionreturns exit code 100 to signal that the remote handled everything —libs/provisionskips local bootstrap
Without --remote, spec.provider and spec.remote are ignored. The
same cluster spec works for both local and remote provisioning — the
mode is driven by the caller, not the file.
| Variable | Default | Description |
|---|---|---|
LOK8S_CLUSTER_NAME |
local |
Cluster name |
KIND_NODE_VERSION |
v1.31.12@sha256:... |
Kind node image |
KIND_CONFIG |
.lok8s/drivers/lo/cluster/config.yaml |
Kind config file |
DOMAIN_NAME |
(empty) | Domain override. Full precedence: --domain flag > DOMAIN_NAME env > clusters/.active > lok8s.dev. When the env var and .active disagree, lo prints a one-line notice naming which won |
DOMAIN_SANS |
* |
Domain SANs |
KIND_EXPERIMENTAL_DOCKER_NETWORK |
lok8s |
Docker network name |
PATH_SECRETS |
.secrets |
Active domain's store — lo build/lo deploy set it to clusters/<domain>/secrets; .secrets only with no domain context |
LOK8S_SERVICE_CONFIG |
(empty) | Service config name for override merging |
DEBUG |
(empty) | Enable debug output when non-empty |
ARGSH_BUILTIN_PATH |
(auto-detected) | Full path to argsh.so for MCP support |