Skip to content

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.

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.

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.

Terminal window
ksail workload validate
ksail workload scan --framework nsa --compliance-threshold 85

When a repository describes several environments, validate each config — a manifest can be valid for one overlay and broken for another:

Terminal window
ksail workload validate
ksail --config ksail.prod.yaml workload validate
ksail --config ksail.prod.yaml workload scan --framework nsa --compliance-threshold 85

scan 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:

  1. Push the source directory as a versioned OCI artifact:

    Terminal window
    ksail --config ksail.prod.yaml workload push
  2. Reconcile — tell the in-cluster GitOps engine to pull that artifact and apply it:

    Terminal window
    ksail --config ksail.prod.yaml workload reconcile
  3. 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.

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: K3s

The 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=integration
InputDefaultDescription
distributionVanillaDistribution to provision (Vanilla, K3s, Talos, VCluster, KWOK)
providerDockerInfrastructure provider; cloud providers need credentials via env vars (HCLOUD_TOKEN, OMNI_SERVICE_ACCOUNT_KEY)
ksail-versionlatestPin a released KSail version for reproducible pipelines
argsExtra CLI arguments passed to cluster init
configUse an existing ksail.yaml instead of scaffolding (init: "false" skips init entirely)
cachetrueCache Helm charts and mirror registry volumes between runs
sops-age-keySOPS age key for decrypting secrets during Flux setup
validate / scanfalsePre-create manifest validation and Kubescape scanning
push / reconcilefalseGitOps push and reconcile after create
deletefalseTear 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.

The composite action is a convenience, not a requirement. Install KSail on any runner image and run the same commands directly:

Terminal window
ksail cluster init --distribution K3s
ksail cluster create

Add --ttl so clusters self-destruct even when a job is cancelled mid-run.

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 push produces.
  • 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.