Deploying to Clusters
lok8s uses a two-step pipeline: build kustomize targets into per-target artifacts, then deploy artifacts to a cluster.
Workload targets have no framework-level ordering — lo build and lo deploy iterate alphabetically. Cluster-infrastructure ordering lives in spec.bootstrap and runs during lo up via the framework bootstrap (.lok8s/libs/bootstrap), before Tilt / workloads. See the Addons guide for the bootstrap model.
Build
lo build [domain] [target...]For each target directory under clusters/<domain>/targets/, runs kustomize build --enable-alpha-plugins and writes the output to clusters/<domain>/artifacts/<target>/artifacts.yaml.
# Build all targets for the active domain
lo build
# Build all targets for a specific domain
lo build example.com
# Build specific targets only
lo build example.com networking monitoringSplit Output
Use --split to produce individual files per resource instead of a single artifacts.yaml:
lo build --split example.comThis creates files like Deployment.default.my-app.yaml, Service.default.my-app.yaml, etc. Useful for GitOps workflows where you want to review individual resources.
Deploy
lo deploy [domain] [target...]Applies per-target artifacts to the cluster. Targets are discovered from clusters/<domain>/targets/<name>/ alphabetically, or supplied explicitly as positional arguments. Workload-plane ordering is intentionally not a framework primitive — kubectl handles in-manifest order; Tilt handles runtime deps via resource_deps; cluster-infra ordering lives in spec.bootstrap.
# Deploy all targets for the active domain
lo deploy
# Deploy a specific domain
lo deploy example.com
# Deploy specific targets
lo deploy example.com networkingDeployment Phases
For each target, deployment follows three phases:
- CRDs first — CustomResourceDefinition resources are extracted and applied separately, then the deploy waits for them to become Established
- Apply resources — all resources in the target's
artifacts.yamlare applied viakubectl apply - Wait for health — waits for all Deployments across all namespaces to become Available (default timeout: 120s)
Label Filtering
Deploy specific resource types using the --filter flag:
# Deploy only system-type resources
lo deploy --filter type=system example.comThis filters resources by lok8s.dev/<key> labels. Resources are matched across all targets.
Deployment Domains
Deployment domains let you deploy content to another domain's cluster. They have a deploy.lok8s.yaml with a clusterRef:
# clusters/api.example.com/deploy.lok8s.yaml
apiVersion: cluster.lok8s.dev/v1beta1
kind: Deploy
metadata:
name: api
spec:
clusterRef:
domain: example.com # deploys to this cluster
namespace: apiNote: Deploy CRD workload selection is being reworked post-refactor. Target selection for Deploy specs will land alongside the
services.yamltargets-map design.
Build and deploy work the same way:
lo build api.example.com
lo deploy api.example.comThe deploy command uses the kubeconfig from the referenced cluster domain.
Full Lifecycle: Provision
The lo provision command runs the full lifecycle for a cluster domain:
lo provision example.com- Creates the cluster (via driver contract)
- Applies
spec.bootstrapaddons via the framework bootstrap - Registers with kubehz (if
spec.kubehz.accessis set)
Workload deployment is handled separately by lo deploy (headless/CI) or Tilt (local dev) — it is not part of provision.
Provisioning from CI
You can run lo provision from GitHub Actions to spin up a committed cluster on every push (or on demand). A reusable workflow ships with lok8s:
# .github/workflows/spinup.yml in your repo
jobs:
spinup:
uses: kernpilot/lok8s/.github/workflows/spinup.yml@main
with:
domain: my-cluster.example.com
secrets:
HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}It needs only HCLOUD_TOKEN (your Hetzner Cloud API token, used to create the infrastructure). The cluster.lok8s.yaml for the domain must already be committed under clusters/<domain>/.
Claiming a registered cluster
When a cluster opts into kubehz dashboard visibility (spec.kubehz.access is set to registered or managed), lo provision registers it as pending and prints its SSH-key MD5 fingerprint — both to the provision log and, in the reusable workflow, to the GitHub job summary.
To attach the cluster to your account, open the dashboard Claim page and provide two things:
- that MD5 fingerprint, and
- your own Hetzner Cloud token — used once to prove you control the account the cluster's SSH key lives in. It is never stored.
No platform/API token is needed in CI: ownership is proven interactively at claim time, not at provision time. You can reproduce the fingerprint locally with:
ssh-keygen -E md5 -lf ~/.ssh/id_ed25519.pubDestroy
lo destroy example.comTears down the cluster via the driver contract's driver::destroy function.