Skip to content

Multi-Environment Workflows with --config

KSail supports two complementary approaches for multi-environment workflows:

  1. Separate config files (--config) β€” a dedicated ksail.<env>.yaml per environment with different settings
  2. Environment variable expansion β€” a single shared ksail.yaml with ${VAR} placeholders substituted at runtime

You can use either approach or combine both. This guide walks through common patterns.

The --config flag overrides KSail’s default config file discovery (which walks up the directory tree looking for ksail.yaml). Pair it with environment-specific files to manage distinct clusters from the same project root.

my-app/
β”œβ”€β”€ ksail.yaml # shared defaults (optional, not used directly)
β”œβ”€β”€ ksail.dev.yaml # local dev cluster
β”œβ”€β”€ ksail.staging.yaml # staging cluster on Hetzner
β”œβ”€β”€ ksail.prod.yaml # production cluster
β”œβ”€β”€ k8s/
β”‚ β”œβ”€β”€ kustomization.yaml
β”‚ └── ...
β”œβ”€β”€ kind.yaml # Kind cluster config (used by ksail.dev.yaml)
└── talos/ # Talos configs (used by ksail.staging.yaml / ksail.prod.yaml)

ksail.dev.yaml β€” local Kind cluster with Flux and a local registry:

# yaml-language-server: $schema=https://raw.githubusercontent.com/devantler-tech/ksail/main/schemas/ksail-config.schema.json
apiVersion: ksail.io/v1alpha1
kind: Cluster
spec:
cluster:
name: dev
distribution: Vanilla
gitOpsEngine: Flux
cni: Default
localRegistry:
registry: localhost:5050
metricsServer: Default
workload:
sourceDirectory: k8s

ksail.staging.yaml β€” Talos cluster on Hetzner with Cilium and Flux:

# yaml-language-server: $schema=https://raw.githubusercontent.com/devantler-tech/ksail/main/schemas/ksail-config.schema.json
apiVersion: ksail.io/v1alpha1
kind: Cluster
spec:
cluster:
name: staging
distribution: Talos
provider: Hetzner
gitOpsEngine: Flux
cni: Cilium
metricsServer: Enabled
workload:
sourceDirectory: k8s

ksail.prod.yaml β€” production Talos cluster on Omni with all security components:

# yaml-language-server: $schema=https://raw.githubusercontent.com/devantler-tech/ksail/main/schemas/ksail-config.schema.json
apiVersion: ksail.io/v1alpha1
kind: Cluster
spec:
cluster:
name: production
distribution: Talos
provider: Omni
gitOpsEngine: Flux
cni: Cilium
policyEngine: Kyverno
certManager: Enabled
metricsServer: Enabled
workload:
sourceDirectory: k8s
Terminal window
# Work on the local dev cluster
ksail --config ksail.dev.yaml cluster create
ksail --config ksail.dev.yaml workload watch
# Validate manifests against staging constraints before promoting
ksail --config ksail.staging.yaml workload validate
# Push manifests to the OCI registry for production GitOps
ksail --config ksail.prod.yaml workload push
# Update a running cluster to match the latest config
ksail --config ksail.staging.yaml cluster update
# Tear down the dev cluster after a session
ksail --config ksail.dev.yaml cluster delete

Approach 2 β€” Shared Config with Environment Variables

Section titled β€œApproach 2 β€” Shared Config with Environment Variables”

Use a single ksail.yaml with ${VAR} placeholders. KSail expands them at runtime from your shell environment. This keeps one file in version control while allowing machine- or environment-specific values.

# yaml-language-server: $schema=https://raw.githubusercontent.com/devantler-tech/ksail/main/schemas/ksail-config.schema.json
apiVersion: ksail.io/v1alpha1
kind: Cluster
spec:
cluster:
name: "${CLUSTER_NAME:-dev}"
distribution: "${CLUSTER_DISTRIBUTION:-Vanilla}"
provider: "${CLUSTER_PROVIDER:-Docker}"
gitOpsEngine: Flux
cni: "${CLUSTER_CNI:-Default}"
localRegistry:
registry: "${LOCAL_REGISTRY:-localhost:5050}"
workload:
sourceDirectory: "${WORKLOAD_DIR:-k8s}"

Switch environments by exporting different variable sets:

Terminal window
# Local dev (default values used)
ksail cluster create
# Staging
export CLUSTER_NAME=staging
export CLUSTER_DISTRIBUTION=Talos
export CLUSTER_PROVIDER=Hetzner
export CLUSTER_CNI=Cilium
ksail cluster update
# CI (inline)
CLUSTER_NAME=ci CLUSTER_DISTRIBUTION=K3s ksail cluster create

For convenience, manage per-environment variables in .env files and source them before running KSail:

.env.dev
export CLUSTER_NAME=dev
export CLUSTER_DISTRIBUTION=Vanilla
export LOCAL_REGISTRY=localhost:5050
.env.staging
export CLUSTER_NAME=staging
export CLUSTER_DISTRIBUTION=Talos
export CLUSTER_PROVIDER=Hetzner
export CLUSTER_CNI=Cilium
Terminal window
source .env.staging && ksail cluster update

Use --config for structural differences between environments (different distributions, providers, or components) and environment variables within each config file for credentials and machine-specific paths:

ksail.dev.yaml:

apiVersion: ksail.io/v1alpha1
kind: Cluster
spec:
cluster:
name: dev
distribution: Vanilla
localRegistry:
registry: "${DEV_REGISTRY:-localhost:5050}"

ksail.prod.yaml:

apiVersion: ksail.io/v1alpha1
kind: Cluster
spec:
cluster:
name: production
distribution: Talos
provider: Omni
omni:
endpoint: "${OMNI_ENDPOINT}"
localRegistry:
registry: "${PROD_REGISTRY}"
Terminal window
# Dev: env vars provide the registry URL
DEV_REGISTRY=localhost:5050 ksail --config ksail.dev.yaml cluster create
# Prod: env vars provide the Omni endpoint and external registry
OMNI_ENDPOINT=https://my-org.omni.siderolabs.io:443 \
PROD_REGISTRY=ghcr.io/myorg/myrepo \
ksail --config ksail.prod.yaml workload push

Pass --config as an argument to the ksail-cluster composite action:

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

For ephemeral test clusters in CI, 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) for the full TTL guide, or PR Preview Clusters for the full action inputs reference and PR preview patterns.

  • Name your clusters after their environment (dev, staging, production) so context names are predictable across machines.

  • Keep distribution-specific config files (kind.yaml, talos/) named consistently β€” ksail cluster init generates them alongside ksail.yaml.

  • Version-control all ksail.<env>.yaml files but keep credentials in environment variables or a secret manager.

  • Use ksail workload validate with the target environment config before promoting manifests. See Substitution Expansion (validate) for how Flux ${VAR} substitutions are handled during validation.

    Terminal window
    ksail --config ksail.staging.yaml workload validate
  • Use ksail cluster update --dry-run to preview the impact of config changes on a running cluster before applying them.