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:
ksail cluster create # local — ksail.yaml is the defaultksail --config ksail.prod.yaml cluster create # productionThere 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.
The pattern
Section titled “The pattern”-
Keep a config per environment
Section titled “Keep a config per environment”Start from your
ksail.yamland 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--configoverrides KSail’s default discovery (which walks up the directory tree looking forksail.yaml), so a single repository root can drive many clusters. -
Keep the distribution, change the provider
Section titled “Keep the distribution, change the provider”For high fidelity, run the same
distributionin every environment and let only theprovider(and the depth of installed components) differ. Bugs then surface locally instead of in production.# ksail.yaml — local: thin test-bed on DockerapiVersion: ksail.io/v1alpha1kind: Clusterspec:cluster:name: localdistribution: Talosprovider: DockergitOpsEngine: Fluxworkload:sourceDirectory: k8skustomizationFile: clusters/local# ksail.prod.yaml — production: full cluster on HetznerapiVersion: ksail.io/v1alpha1kind: Clusterspec:cluster:name: productiondistribution: Talosprovider: HetznergitOpsEngine: Fluxcni: CiliumcertManager: EnabledpolicyEngine: Kyvernoprovider:hetzner:location: "fsn1"workload:sourceDirectory: k8skustomizationFile: clusters/prodThe configs differ only where the environments genuinely differ. Everything else is shared.
-
Share one manifest tree, overlay per cluster
Section titled “Share one manifest tree, overlay per cluster”Both configs point
spec.workload.sourceDirectoryat the samek8s/tree. As the repo grows, factor that tree into layers — sharedbases/, thenproviders/overlays, then a thinclusters/<env>overlay per environment — and aim each config’sspec.workload.kustomizationFileat its cluster overlay:ksail.yaml workload:kustomizationFile: clusters/localksail.prod.yaml workload:kustomizationFile: clusters/prodThis keeps shared definitions in one place while each environment stays a thin overlay. See Project Structure & GitOps Layout for the full layout.
-
Keep credentials out of Git with
Section titled “Keep credentials out of Git with ${VAR}”${VAR}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 Gitspec:cluster:localRegistry:registry: "user:${GHCR_TOKEN}@ghcr.io/org/platform/manifests"Terminal window GHCR_TOKEN=… ksail --config ksail.prod.yaml workload push -
Deliver and operate, per environment
Section titled “Deliver and operate, per environment”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 environmentksail --config ksail.prod.yaml cluster createksail --config ksail.prod.yaml cluster update# Deliver manifests via GitOps: push the OCI artifact, then reconcileksail --config ksail.prod.yaml workload pushksail --config ksail.prod.yaml workload reconcile# Gate changes offline — no cluster required, ideal in CIksail --config ksail.prod.yaml workload validateksail --config ksail.prod.yaml workload scan
Result
Section titled “Result”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.
In CI/CD
Section titled “In CI/CD”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:
ksail --config ksail.ci.yaml cluster create --ttl 30m# run tests...ksail --config ksail.ci.yaml cluster deleteSee Ephemeral Clusters (–ttl) and PR Preview Clusters for the full action inputs and preview patterns.
Related
Section titled “Related”- Deliver with GitOps — the push/reconcile loop these configs drive.
- Project Structure & GitOps Layout — the layered
bases/→providers/→clusters/tree behindkustomizationFile. - Reference Architecture — a complete two-environment platform built on this pattern.
- GitOps Workflows — Flux and ArgoCD in depth.
- Secret Management — SOPS-encrypted secrets, one key per environment.
- CI/CD Integration — wiring
validate/scan/push/reconcileinto pipelines. - Configuration reference · Cluster flags · Workload flags