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.

  • Docker Desktop or Docker Engine installed and running
  • docker ps command works
Terminal window
ksail cluster init \
--name my-cluster \
--distribution K3s \
--control-planes 1 \
--workers 2

This creates:

  • ksail.yaml — KSail configuration
  • k3d.yaml — K3d-specific cluster configuration
Terminal window
ksail cluster create
Terminal window
ksail cluster info
kubectl get nodes
Terminal window
kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --port=80 --type=LoadBalancer
kubectl get svc nginx # EXTERNAL-IP appears within seconds via ServiceLB
Terminal window
ksail cluster delete
# 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:

ComponentNotes
ServiceLB (Klipper-LB)LoadBalancer support via host ports
local-path-provisionerDynamic PersistentVolume provisioning
Metrics Serverkubectl top and HPA support
CoreDNSCluster DNS
TraefikIngress 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
# k3d.yaml — disable built-in Flannel
options:
k3s:
extraArgs:
- arg: --flannel-backend=none
nodeFilters: ["server:*"]
- arg: --disable-network-policy
nodeFilters: ["server:*"]

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

image: rancher/k3s:v1.31.0-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.