Skip to content

Multi-Environment Workflows with --config

KSail’s recommended pattern for running many environments is simple: one config file per environment, selected with --config. You keep ksail.yaml (e.g. local on Docker) and ksail.prod.yaml (e.g. production on a cloud provider) side by side in one repository, and every command targets an environment by pointing at its config:

Terminal window
ksail cluster create # local — ksail.yaml is the default
ksail --config ksail.prod.yaml cluster create # production

There is no separate “prod CLI” and no per-environment workflow to learn — create, update, workload push, workload reconcile, workload validate, and workload scan all take the same --config. This guide walks through the pattern end to end.

  1. Start from your ksail.yaml and add a sibling for each additional environment. Name them after the environment so the intent is obvious and context names stay predictable across machines:

    my-platform/
    ├── ksail.yaml # local — Talos on Docker
    ├── ksail.prod.yaml # production — Talos on Hetzner
    ├── k8s/ # one shared manifest tree (see step 3)
    └── talos/ # shared Talos patches

    --config overrides KSail’s default discovery (which walks up the directory tree looking for ksail.yaml), so a single repository root can drive many clusters.

  2. Keep the distribution, change the provider

    Section titled “Keep the distribution, change the provider”

    For high fidelity, run the same distribution in every environment and let only the provider (and the depth of installed components) differ. Bugs then surface locally instead of in production.

    # ksail.yaml — local: thin test-bed on Docker
    apiVersion: ksail.io/v1alpha1
    kind: Cluster
    spec:
    cluster:
    name: local
    distribution: Talos
    provider: Docker
    gitOpsEngine: Flux
    workload:
    sourceDirectory: k8s
    kustomizationFile: clusters/local
    # ksail.prod.yaml — production: full cluster on Hetzner
    apiVersion: ksail.io/v1alpha1
    kind: Cluster
    spec:
    cluster:
    name: production
    distribution: Talos
    provider: Hetzner
    gitOpsEngine: Flux
    cni: Cilium
    certManager: Enabled
    policyEngine: Kyverno
    provider:
    hetzner:
    location: "fsn1"
    workload:
    sourceDirectory: k8s
    kustomizationFile: clusters/prod

    The configs differ only where the environments genuinely differ. Everything else is shared.

  3. Share one manifest tree, overlay per cluster

    Section titled “Share one manifest tree, overlay per cluster”

    Both configs point spec.workload.sourceDirectory at the same k8s/ tree. As the repo grows, factor that tree into layers — shared bases/, then providers/ overlays, then a thin clusters/<env> overlay per environment — and aim each config’s spec.workload.kustomizationFile at its cluster overlay:

    ksail.yaml
    workload:
    kustomizationFile: clusters/local
    ksail.prod.yaml
    workload:
    kustomizationFile: clusters/prod

    This keeps shared definitions in one place while each environment stays a thin overlay. See Project Structure & GitOps Layout for the full layout.

  4. Configs support ${VAR} environment-variable expansion, substituted at load time. Commit the placeholder, supply the value from your shell or CI secret store — so a single config stays credential-free and safe to version-control:

    # ksail.prod.yaml (excerpt) — token never touches Git
    spec:
    cluster:
    localRegistry:
    registry: "user:${GHCR_TOKEN}@ghcr.io/org/platform/manifests"
    Terminal window
    GHCR_TOKEN= ksail --config ksail.prod.yaml workload push
  5. Every operation is the same command with a different --config. Provision and reconcile through the GitOps loop, and gate changes offline before they reach a cluster:

    Terminal window
    # Provision (or reconcile node config on) an environment
    ksail --config ksail.prod.yaml cluster create
    ksail --config ksail.prod.yaml cluster update
    # Deliver manifests via GitOps: push the OCI artifact, then reconcile
    ksail --config ksail.prod.yaml workload push
    ksail --config ksail.prod.yaml workload reconcile
    # Gate changes offline — no cluster required, ideal in CI
    ksail --config ksail.prod.yaml workload validate
    ksail --config ksail.prod.yaml workload scan

One repository describes every environment. The same handful of commands carry a change from your laptop to production — only the --config argument changes — while shared manifests and a credential-free Git history keep the environments in lockstep without duplication.

CI is the same --config discipline. Validate and scan every environment on each pull request, then push and reconcile on merge — secrets arrive as masked variables that ${VAR} expansion resolves:

- uses: devantler-tech/ksail/.github/actions/ksail-cluster@main
with:
config: ksail.prod.yaml # the environment-specific config
push: "true" # push manifests for GitOps
reconcile: "true" # trigger the reconcile step
env:
GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }}

For ephemeral test or preview clusters, combine --config with --ttl as a safety net:

Terminal window
ksail --config ksail.ci.yaml cluster create --ttl 30m
# run tests...
ksail --config ksail.ci.yaml cluster delete

See Ephemeral Clusters (–ttl) and PR Preview Clusters for the full action inputs and preview patterns.