Skip to content

Declarative Configuration

KSail uses declarative YAML configuration files for reproducible cluster setup. This page describes ksail.yaml — the project-level configuration file that defines your cluster's desired state.

Each KSail project includes a ksail.yaml file describing cluster distribution, networking, components, and workload configuration. Run ksail cluster init to generate it — commit to version control to share with your team.

KSail supports environment variable expansion in all string configuration values using the ${VAR_NAME} syntax for secure credentials, environment-specific paths, and dynamic values.

Basic syntax: ${VARIABLE_NAME} — Reference an environment variable. If not set, expands to an empty string and logs a warning.

Default value syntax: ${VARIABLE_NAME:-default} — Use a default value if the variable is not set. No warning is logged when using defaults.

spec:
editor: "${EDITOR:-vim}"
cluster:
connection:
kubeconfig: "${HOME}/.kube/config"
context: "${KUBE_CONTEXT:-kind-kind}"
distributionConfig: "${CONFIG_DIR:-configs}/kind.yaml"
localRegistry:
registry: "${REGISTRY:-localhost:5000}"
vanilla:
mirrorsDir: "${MIRRORS_DIR:-mirrors}"
talos:
config: "${TALOS_CONFIG_PATH:-~/.talos/config}"
provider:
hetzner:
sshKeyName: "${HCLOUD_SSH_KEY}"
workload:
sourceDirectory: "${WORKLOAD_DIR:-k8s}"
chat:
model: "${CHAT_MODEL:-gpt-4o}"

| Syntax | Variable Set | Variable Not Set | | ----------------- | ------------ | ------------------------- | | ${VAR} | Uses value | Empty string + warning | | ${VAR:-default} | Uses value | Uses default (no warning) | | ${VAR:-} | Uses value | Empty string (no warning) |

Environment variables are expanded in all string fields of ksail.yaml, distribution configs (kind.yaml, k3d.yaml), and Talos patch files (talos/cluster/, talos/control-planes/, talos/workers/):

# kind.yaml - Environment variables are expanded before parsing
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."${REGISTRY:-localhost:5000}"]
endpoint = ["http://${REGISTRY:-localhost:5000}"]
# talos/cluster/registry.yaml - Environment variables are expanded
machine:
registries:
mirrors:
docker.io:
endpoints:
- http://${REGISTRY:-localhost:5000}

Because patch files are expanded too, you can inject registry credentials into the Talos machine config as config-as-code — keeping the secret in the environment, never committed. This is the supported way to give nodes registry auth (for example, so containerd can fetch cosign signatures for private packages during image verification) without passing credentials via CLI flags:

# talos/cluster/registry-auth.yaml - Credentials are injected from the environment at load time
machine:
registries:
config:
ghcr.io:
auth:
username: my-user
password: ${GHCR_TOKEN} # expanded at load; commit the placeholder, not the token
spec:
cluster:
localRegistry:
registry: "${REGISTRY_USER}:${REGISTRY_PASS}@${REGISTRY_HOST:-ghcr.io}/myorg/myrepo"
Terminal window
export REGISTRY_USER="github-user"
export REGISTRY_PASS="ghp_secrettoken123"
ksail cluster create
spec:
cluster:
connection:
context: "${CLUSTER_NAME:-kind-kind}"
distributionConfig: "${ENV:-dev}/kind.yaml"
workload:
sourceDirectory: "${ENV:-dev}/k8s"
Terminal window
# Development (using defaults)
ksail cluster create
# Production (override with environment variables)
export ENV="prod"
export CLUSTER_NAME="prod-cluster"
ksail cluster create
# 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:
distribution: Vanilla
distributionConfig: kind.yaml

This minimal configuration creates a Vanilla cluster (implemented with Kind) using defaults for all other settings.

# yaml-language-server: $schema=https://raw.githubusercontent.com/devantler-tech/ksail/main/schemas/ksail-config.schema.json
apiVersion: ksail.io/v1alpha1
kind: Cluster
spec:
editor: code --wait
cluster:
distribution: Vanilla
distributionConfig: kind.yaml
connection:
kubeconfig: ~/.kube/config
context: kind-kind
timeout: 5m
cni: Cilium
csi: Default
metricsServer: Enabled
certManager: Enabled
policyEngine: Kyverno
localRegistry:
registry: localhost:5050
gitOpsEngine: Flux
workload:
sourceDirectory: k8s
validateOnPush: true

| Field | Type | Required | Description | | ----- | ---- | -------- | ----------- | | apiVersion | string | Yes | Must be ksail.io/v1alpha1 | | kind | string | Yes | Must be Cluster | | spec | object | Yes | Cluster and workload specification (see below) |

The spec field is a Spec object that defines editor, cluster, and workload configuration.

| Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | | editor | string | – | Editor command for interactive workflows (e.g. code --wait) | | cluster | ClusterSpec | – | | | provider | ProviderSpec | – | | | workload | WorkloadSpec | – | | | chat | ChatSpec | – | |

Editor command for interactive workflows (e.g., code --wait, vim). Falls back to SOPS_EDITOR, KUBE_EDITOR, EDITOR, VISUAL, or system defaults.

| Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | | distributionConfig | string | – | | | connection | Connection | – | | | distribution | enum | – | | | provider | enum | – | | | cni | enum | – | | | csi | enum | – | | | cdi | enum | – | | | metricsServer | enum | – | | | loadBalancer | enum | – | | | certManager | enum | – | | | policyEngine | enum | – | | | localRegistry | LocalRegistry | – | | | gitOpsEngine | enum | – | | | sops | SOPS | – | | | nodeAutoscaling | enum | – | Deprecated. Use autoscaler.node.enabled instead. Do not set both nodeAutoscaling and autoscaler. | | autoscaler | AutoscalerConfig | – | Pod and node autoscaling configuration (supersedes deprecated nodeAutoscaling) | | importImages | string | – | Path to tar archive with container images to import after cluster creation but before component installation | | controlPlanes | int32 | 1 | Number of control-plane nodes to create for the cluster (provider/distribution-agnostic) | | workers | int32 | – | Number of worker nodes to create for the cluster (provider/distribution-agnostic) | | kubernetesVersion | string | – | Kubernetes version to deploy. When set: cluster create/update reconcile toward it. When unset: cluster update follows the latest stable version and new clusters use a default compatible with the pinned Talos version. | | oidc | OIDCSpec | – | OIDC authentication configuration for the API server and kubeconfig | | vanilla | OptionsVanilla | – | | | talos | OptionsTalos | – | |

See Distributions for detailed information.

  • Vanilla (default) – Standard upstream Kubernetes via Kind
  • K3s – Lightweight Kubernetes via K3d
  • TalosTalos Linux in Docker containers or Hetzner Cloud servers
  • VCluster – Virtual clusters via vCluster
  • KWOK – Simulated clusters via KWOK (control-plane only, no real workloads)
  • EKS – Amazon Elastic Kubernetes Service via eksctl (requires AWS credentials and the eksctl CLI on PATH)

See Providers for more details.

  • Docker (default) – Run nodes as Docker containers (local development)
  • Hetzner – Run nodes on Hetzner Cloud servers (requires HCLOUD_TOKEN)
  • Omni – Manage Talos cluster nodes through Sidero Omni
  • AWS – Manage EKS clusters on Amazon Web Services (requires standard AWS SDK credentials)

Path to the distribution-specific configuration file or directory. This tells KSail where to find settings like node counts, port mappings, and distribution-specific features.

Default values by distribution:

  • Vanillakind.yaml
  • K3sk3d.yaml
  • Talostalos/ (directory)
  • VClustervcluster.yaml
  • KWOKkwok/ (directory)
  • EKSeks.yaml

See Distribution Configuration below for details on each format.

| Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | | kubeconfig | string | ~/.kube/config | Path to kubeconfig file | | context | string | (derived) | Kubeconfig context name | | timeout | duration | – | Timeout for cluster operations |

Context defaults by distribution:

  • Vanillakind-kind
  • K3sk3d-k3d-default
  • Talos (Docker/Hetzner) → admin@talos-default
  • Talos (Omni) → the context name generated by Omni (e.g., devantler-prod)
  • VClustervcluster-docker_vcluster-default
  • KWOKkwok-kwok-default

When using Talos with Omni, Omni generates the context name; set spec.cluster.connection.context to that generated name.

Timeout format: Go duration string (e.g., 30s, 5m, 1h)

See CNI for more details.

  • Default (default) – Uses the distribution's built-in CNI (kindnetd for Vanilla, flannel for K3s)
  • Cilium – Installs Cilium for advanced networking and observability
  • Calico – Installs Calico for network policies

See CSI for more details.

  • Default (default) – Uses the distribution × provider's default behavior:
    • K3s: includes local-path-provisioner
    • Vanilla/Talos × Docker: no CSI
    • Talos × Hetzner: includes Hetzner CSI driver
  • Enabled – Explicitly installs CSI driver (local-path-provisioner for local clusters, Hetzner CSI for Talos × Hetzner)
  • Disabled – Disables CSI installation (for K3s, this disables the default local-storage)

Whether to install metrics-server for resource metrics.

  • Default (default) – Uses distribution's default behavior (K3s includes metrics-server; Vanilla and Talos do not)
  • Enabled – Install metrics-server
  • Disabled – Skip installation

When metrics-server is enabled on Vanilla or Talos, KSail automatically:

  1. Configures kubelet certificate rotation (serverTLSBootstrap: true)
  2. Installs kubelet-csr-approver to approve certificate requests
  3. Deploys metrics-server with secure TLS communication

Whether to install cert-manager for TLS certificate management.

  • Enabled – Install cert-manager
  • Disabled (default) – Skip installation

Policy engine to install for enforcing security, compliance, and best practices. See Policy Engines for details.

Registry configuration for GitOps workflows. Supports local Docker registries or external registries with authentication.

Format: [user:pass@]host[:port][/path]

Examples:

  • localhost:5050 – Local Docker registry
  • ghcr.io/myorg/myrepo – GitHub Container Registry
  • ${USER}:${PASS}@ghcr.io:443/myorg – With credentials from environment variables

GitOps engine for continuous deployment. See GitOps. When set to Flux or ArgoCD, KSail scaffolds a GitOps CR into your source directory.

  • None (default) – No GitOps engine
  • Flux – Install Flux CD and scaffold FluxInstance CR
  • ArgoCD – Install Argo CD and scaffold Application CR

Advanced configuration options are direct fields under spec.cluster. See Schema Support for the complete structure.

Talos options (spec.cluster.talos):

  • controlPlanes – Number of control-plane nodes (default: 1)
  • workers – Number of worker nodes (default: 0)
  • config – Path to talosconfig file (default: ~/.talos/config)
  • version – Pin the Talos OS version (e.g. v1.12.4); caps upgrades and selects the node image (default: built-in)
  • iso – Cloud provider ISO/image ID for Talos Linux (default: 125127 for Talos 1.12.4 x86; for ARM, look up the matching ISO ID under Images → ISOs in the Hetzner Cloud Console)

The Kubernetes version is set at the top level (spec.cluster.kubernetesVersion), not under talos:

  • kubernetesVersion – Pin the Kubernetes version (e.g. v1.32.0). When unset, cluster update keeps the version already running (no unrequested upgrade) and new clusters default to one compatible with the pinned talos.version

Hetzner options (spec.provider.hetzner):

  • controlPlaneServerType – Server type for control-plane nodes (default: cx23)
  • workerServerType – Server type for worker nodes (default: cx23)
  • location – Datacenter location: fsn1, nbg1, hel1 (default: fsn1)
  • networkName – Private network name (default: <cluster>-network)
  • networkCidr – Network CIDR block (default: 10.0.0.0/16)
  • sshKeyName – SSH key name for server access (optional)
  • tokenEnvVar – Environment variable for API token (default: HCLOUD_TOKEN)
  • fallbackLocations – Alternative locations to try when the primary is at capacity (default: nbg1, hel1)

Vanilla options (spec.cluster.vanilla):

  • mirrorsDir – Directory for containerd host mirror configuration

| Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | | sourceDirectory | string | k8s | Path to the directory containing Kubernetes manifests. Used as the default path by validate, watch, and push when no explicit path argument is given. | | validateOnPush | boolean | false | Validate manifests against schemas before pushing (validation disabled by default) | | tag | string | dev | OCI artifact tag used for workload push and GitOps reconciliation (Flux OCIRepository and ArgoCD Application). Push priority: CLI oci:// ref > this field > registry-embedded tag > dev. Reconciliation priority: this field > registry-embedded tag > dev | | kustomizationFile | string | – | Path to the kustomization directory relative to sourceDirectory. When set, Flux Sync.Path is configured to this path so Flux uses the specified kustomization as the entry point instead of requiring a root kustomization.yaml. | | flux | FluxConfig | – | Flux bootstrap configuration: operator/distribution version pins and signature verification for the generated OCIRepository. Empty values use KSail's pinned versions; a GitOps repo that declares these becomes the steady-state owner. | | watch | WatchConfig | – | Configuration for the workload watch command (pre-apply hooks, etc.) | | validation | ValidationConfig | – | Configuration for the workload validate command (additional kinds to skip, etc.). |

| Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | | model | string | – | Chat model (empty or 'auto' for API default) | | reasoningEffort | string | – | Reasoning effort level for chat responses (low, medium, or high) |

KSail references distribution-specific configuration files to customize cluster behavior. The path to these files is set via spec.cluster.distributionConfig.

Vanilla (implemented with Kind) Configuration

Section titled “Vanilla (implemented with Kind) Configuration”

Default: kind.yaml

See Kind Configuration for the full schema.

Example:

kind.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30000
hostPort: 30000

Default: k3d.yaml

See K3d Configuration for the full schema.

Example:

k3d.yaml
apiVersion: k3d.io/v1alpha5
kind: Simple
servers: 1
agents: 2
ports:
- port: 8080:80
nodeFilters:
- loadbalancer

Default: talos/ directory

Talos uses a directory structure for Talos machine configuration patches. Place YAML patch files in talos/cluster/ (all nodes), talos/control-planes/, or talos/workers/:

# talos/cluster/kubelet.yaml (applies to all nodes)
machine:
kubelet:
extraArgs:
max-pods: "250"

See Talos Configuration Reference for patch syntax. Use spec.cluster.talos to configure node counts:

spec:
cluster:
distribution: Talos
distributionConfig: talos
talos:
controlPlanes: 3
workers: 2

On macOS, Docker runs in a Linux VM, so MetalLB virtual IPs are not accessible from the host. Use extraPortMappings to expose container ports directly:

spec:
cluster:
distribution: Talos
talos:
extraPortMappings:
- containerPort: 80
hostPort: 8080
protocol: TCP
- containerPort: 443
hostPort: 8443
protocol: TCP

Access services at http://localhost:8080. Ports are exposed on the first control-plane node; in multi-control-plane clusters, extraPortMappings apply only to that node.

KSail provides a JSON Schema for IDE validation and autocompletion. Reference it at the top of your ksail.yaml:

# yaml-language-server: $schema=https://raw.githubusercontent.com/devantler-tech/ksail/main/schemas/ksail-config.schema.json
apiVersion: ksail.io/v1alpha1
kind: Cluster
spec:
# ...

IDEs with YAML language support (e.g., VS Code + Red Hat YAML extension) provide field autocompletion, inline docs, validation, and enum suggestions.