CI/CD Integration
KSail is a single static binary, so CI/CD is just the same commands you run on your laptop —
workload validate, workload scan, workload push, workload reconcile, cluster update —
wired into a pipeline. This guide shows the recommended shape: a fast offline gate on every pull
request, ordered delivery on merge, and how to spin up real clusters in GitHub Actions when a job
actually needs one.
The shape
Section titled “The shape” pull request merge ┌─────────────────────────┐ ┌──────────────────────────────────────────┐ │ validate + scan │ │ push ──► reconcile ──► cluster update │ │ (offline, no cluster) │ │ (manifests first) (node config last)│ └─────────────────────────┘ └──────────────────────────────────────────┘- On every pull request, gate the change offline — no cluster, no credentials beyond what rendering needs.
- On merge, deliver manifests first, then sync node configuration, so an unreachable node can never block application delivery.
1. Gate every pull request — offline
Section titled “1. Gate every pull request — offline”ksail workload validate (schema validation via kubeconform, with Flux ${var} substitution resolved
from a local schema cache) and ksail workload scan (security posture via Kubescape)
both run fully offline, without a cluster. That makes them an ideal PR gate: fast, deterministic,
and free of cloud access.
ksail workload validateksail workload scan --framework nsa --compliance-threshold 85When a repository describes several environments, validate each config — a manifest can be valid for one overlay and broken for another:
ksail workload validateksail --config ksail.prod.yaml workload validateksail --config ksail.prod.yaml workload scan --framework nsa --compliance-threshold 85scan fails the job when the compliance score drops below --compliance-threshold, so a regression in
security posture blocks the merge. Both commands also read their defaults from spec.workload in
ksail.yaml, so ksail workload scan with no flags is a turnkey gate once configured.
2. Deliver on merge — manifests first, node config last
Section titled “2. Deliver on merge — manifests first, node config last”Once a change lands, deliver it. KSail keeps two concerns separate and orders them deliberately:
-
Push the source directory as a versioned OCI artifact:
Terminal window ksail --config ksail.prod.yaml workload push -
Reconcile — tell the in-cluster GitOps engine to pull that artifact and apply it:
Terminal window ksail --config ksail.prod.yaml workload reconcile -
Update node configuration last — only after the application has shipped:
Terminal window ksail --config ksail.prod.yaml cluster update
Run cluster update last on purpose. Pushing and reconciling manifests talks to the GitOps engine
over the Kubernetes API; updating node configuration touches the underlying nodes, which can be slow or
temporarily unreachable. Ordering delivery before node sync means a stuck or offline node can never
block application delivery — the manifests are already on their way. This is the same ordering described
in the Reference Architecture.
3. Provision a cluster in GitHub Actions
Section titled “3. Provision a cluster in GitHub Actions”The gate above needs no cluster. When a job does — integration tests, smoke tests, a per-PR preview —
the ksail-cluster composite action sets up KSail, caches Helm charts and mirror-registry images
between runs, and runs init + create in one step:
- uses: devantler-tech/ksail/.github/actions/ksail-cluster@main with: distribution: K3sThe action outputs kubeconfig — the path to the provisioned cluster’s kubeconfig — so follow-up steps
can talk to it with any tool. Pair it with --ttl as a cleanup safety
net so the cluster self-destructs even if a job is cancelled mid-run.
jobs: integration: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: devantler-tech/ksail/.github/actions/ksail-cluster@main id: cluster with: distribution: Vanilla - name: Run tests against the cluster env: KUBECONFIG: ${{ steps.cluster.outputs.kubeconfig }} run: go test ./... -tags=integrationValidate manifests, create the cluster, push to its local registry, and wait for the GitOps engine to reconcile — all from inputs:
- uses: devantler-tech/ksail/.github/actions/ksail-cluster@main with: distribution: K3s validate: "true" # ksail workload validate before create push: "true" # ksail workload push after create reconcile: "true" # ksail workload reconcile and wait sops-age-key: ${{ secrets.SOPS_AGE_KEY }}Run Kubescape and upload results to GitHub Code Scanning:
- uses: devantler-tech/ksail/.github/actions/ksail-cluster@main with: distribution: Vanilla scan: "true" scan-framework: nsa,mitre scan-format: sarif scan-output: results.sarif- uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarifKey inputs
Section titled “Key inputs”| Input | Default | Description |
|---|---|---|
distribution | Vanilla | Distribution to provision (Vanilla, K3s, Talos, VCluster, KWOK) |
provider | Docker | Infrastructure provider; cloud providers need credentials via env vars (HCLOUD_TOKEN, OMNI_SERVICE_ACCOUNT_KEY) |
ksail-version | latest | Pin a released KSail version for reproducible pipelines |
args | — | Extra CLI arguments passed to cluster init |
config | — | Use an existing ksail.yaml instead of scaffolding (init: "false" skips init entirely) |
cache | true | Cache Helm charts and mirror registry volumes between runs |
sops-age-key | — | SOPS age key for decrypting secrets during Flux setup |
validate / scan | false | Pre-create manifest validation and Kubescape scanning |
push / reconcile | false | GitOps push and reconcile after create |
delete | false | Tear the cluster down at the end (runs even on failure) |
See the action source for the complete input list, including scan thresholds and host customization.
Beyond GitHub Actions
Section titled “Beyond GitHub Actions”The composite action is a convenience, not a requirement. Install KSail on any runner image and run the same commands directly:
ksail cluster init --distribution K3sksail cluster createAdd --ttl so clusters self-destruct even when a job is cancelled
mid-run.
Hardening the supply chain (optional)
Section titled “Hardening the supply chain (optional)”The gate-and-deliver flow above is complete on its own. When you need a verifiable supply chain, layer these on top — they’re standard, vendor-agnostic practices that build on KSail’s OCI delivery rather than replacing it:
- Sign the OCI artifact that
workload pushproduces. - Attach an SBOM and a provenance attestation to it.
- Have the GitOps engine verify the signature before it applies the artifact, so an unsigned or tampered artifact never reaches the cluster.
This is optional and orthogonal to the recommended shape — adopt it when your compliance posture calls for it.
Related
Section titled “Related”- Deliver with GitOps — the push/reconcile loop these pipelines automate
- Reference Architecture — the full multi-environment delivery pipeline
- Multi-Environment Workflows —
--configper environment - PR Preview Clusters — per-PR ephemeral clusters in GitHub Actions
- Ephemeral Clusters —
--ttlauto-destruction for CI safety nets - Secret Management — SOPS keys and
${VAR}tokens in CI