Skip to content

K3s (K3d)

K3s is a lightweight, certified Kubernetes distribution for resource-constrained environments. K3d runs K3s clusters in Docker containers — each node is a container, with a dedicated load balancer container routing traffic to control planes — providing fast 15–30 s cluster creation with batteries-included defaults (ServiceLB, local-path-provisioner, metrics-server), standard k3d.yaml files, and no vendor lock-in.

K3s (K3d) is ideal for quick local development, CI/CD pipelines with ephemeral clusters, resource-constrained machines or CI runners, and multi-node topology testing.

Requires Docker (installation prerequisites).

Terminal window
ksail cluster init --name my-cluster --distribution K3s --control-planes 1 --workers 2
ksail cluster create
ksail cluster info && kubectl get nodes # verify
ksail cluster delete # cleanup

ksail cluster init creates ksail.yaml (KSail config) and k3d.yaml (K3d cluster config).

# 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: K3s
distributionConfig: k3d.yaml
apiVersion: k3d.io/v1alpha5
kind: Simple
servers: 1 # control-plane nodes
agents: 2 # worker nodes
ports:
- port: 8080:80@loadbalancer

K3s includes these components by default—KSail does not reinstall them:

| Component | Notes | |-----------|-------| | ServiceLB (Klipper-LB) | LoadBalancer support via host ports | | local-path-provisioner | Dynamic PersistentVolume provisioning | | Metrics Server | kubectl top and HPA support | | CoreDNS | Cluster DNS | | Traefik | Ingress controller (can be disabled) |

Configure registry mirrors to avoid rate limits. KSail enables docker.io, ghcr.io, quay.io, and registry.k8s.io mirrors by default. Override with --mirror-registry flags:

Terminal window
ksail cluster init --name my-cluster --distribution K3s \
--mirror-registry 'docker.io=https://registry-1.docker.io' \
--mirror-registry 'ghcr.io=https://ghcr.io' \
--mirror-registry 'quay.io=https://quay.io' \
--mirror-registry 'registry.k8s.io=https://registry.k8s.io'

K3d injects registry configuration so all nodes use your mirrors automatically.

Symptom: ksail cluster create fails with Docker errors.

Terminal window
docker ps # Check Docker is running
df -h # Check available disk space
docker system prune -f # WARNING: removes unused containers, networks, images, and build cache
ksail cluster create

Symptom: kubectl get nodes shows NotReady status.

Terminal window
kubectl describe node <node-name>
kubectl get pods -n kube-system
ksail cluster delete && ksail cluster create

K3s uses ServiceLB (Klipper-LB), which binds to host ports. Ensure the port is not already in use and update ports in k3d.yaml (e.g., 9080:80@loadbalancer) if needed.

Terminal window
docker ps --format "table {{.Names}}\t{{.Ports}}" | grep k3d

Symptom: address already in use error during cluster creation.

Terminal window
k3d cluster list
k3d cluster delete <cluster-name>
# Or initialize with a different name:
ksail cluster init --name my-cluster-2 --distribution K3s

Reduce node count in ksail.yaml (workers: 0 for a single-node cluster) and run docker system prune. Increase Docker Desktop resources under Settings → Resources.

Unlike Vanilla (Kind), K3s supports in-place worker node scaling. Update agents in k3d.yaml and run ksail cluster update to add or remove worker containers without recreation.

k3d.yaml
options:
k3s:
extraArgs:
- arg: --disable=traefik # Remove Traefik ingress
nodeFilters: ["server:*"]
- arg: --disable=servicelb # Remove ServiceLB (use MetalLB instead)
nodeFilters: ["server:*"]
ksail.yaml
spec:
cluster:
cni: Cilium

When cni: Cilium is set, KSail automatically passes --flannel-backend=none, --disable-network-policy, and --disable=traefik to the K3s server. Traefik is disabled because Cilium installs Gateway API CRDs (e.g. backendtlspolicies.gateway.networking.k8s.io) that conflict with Traefik's CRD ownership, causing the K3s helm-install-traefik pod to enter CrashLoopBackOff. You do not need to add these flags manually in k3d.yaml.

K3s version is determined by the K3d image. Pin a specific version in k3d.yaml (see K3d image tags):

image: rancher/k3s:v1.35.3-k3s1
ports:
- port: 8080:80@loadbalancer # HTTP
- port: 8443:443@loadbalancer # HTTPS
- port: 30080:30080@agent:0 # NodePort on first worker

KSail generates standard k3d.yaml files—use K3d CLI directly (k3d cluster create --config k3d.yaml) if needed. No vendor lock-in: configurations are portable and KSail can interact with any K3d cluster accessible via your kubeconfig.