📚Book Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) — free book giveaway!RSVP Booking.com Event
Troubleshooting beginner ⏱ 15 minutes K8s 1.28+

How to Run Kubernetes in Docker (kind)

Create local Kubernetes clusters using kind (Kubernetes in Docker). Set up multi-node clusters, configure networking, and test applications locally.

By Luca Berton 📖 6 min read

💡 Quick Answer: Install kind (brew install kind), then create a cluster with kind create cluster --name my-cluster. For multi-node: use a config file with nodes: section specifying control-plane and workers. Kind uses Docker containers as nodes—perfect for local development and CI/CD.

Key command: kind create cluster --config kind-config.yaml; delete with kind delete cluster --name my-cluster.

Gotcha: kind clusters don’t persist across Docker restarts by default; use extraMounts for persistent volumes and extraPortMappings for ingress.

kind (Kubernetes IN Docker) runs Kubernetes clusters using Docker containers as nodes. It’s perfect for local development, testing, and CI/CD pipelines.

Install kind

# macOS with Homebrew
brew install kind

# Linux
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind

# Windows with Chocolatey
choco install kind

# Verify installation
kind version

Create Basic Cluster

# Create cluster with default name "kind"
kind create cluster

# Create cluster with custom name
kind create cluster --name my-cluster

# Create with specific Kubernetes version
kind create cluster --image kindest/node:v1.28.0

# List clusters
kind get clusters

# Get cluster info
kubectl cluster-info --context kind-my-cluster

Multi-Node Cluster

# multi-node-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
  - role: worker
  - role: worker
  - role: worker
kind create cluster --config multi-node-config.yaml
kubectl get nodes

High Availability Cluster

# ha-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
  - role: control-plane
  - role: control-plane
  - role: worker
  - role: worker
kind create cluster --name ha-cluster --config ha-config.yaml

Expose Ports (Ingress)

# ingress-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    kubeadmConfigPatches:
      - |
        kind: InitConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            node-labels: "ingress-ready=true"
    extraPortMappings:
      - containerPort: 80
        hostPort: 80
        protocol: TCP
      - containerPort: 443
        hostPort: 443
        protocol: TCP
# Create cluster with ingress support
kind create cluster --config ingress-config.yaml

# Install NGINX Ingress Controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml

# Wait for ingress controller
kubectl wait --namespace ingress-nginx \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/component=controller \
  --timeout=90s

NodePort Access

# nodeport-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 30000
        hostPort: 30000
      - containerPort: 30001
        hostPort: 30001
# Create NodePort service accessible on localhost:30000
kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --type=NodePort --port=80 --node-port=30000
curl localhost:30000

Mount Host Directory

# mount-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    extraMounts:
      - hostPath: /path/on/host
        containerPath: /data
        readOnly: false
  - role: worker
    extraMounts:
      - hostPath: /path/on/host
        containerPath: /data

Load Docker Image

# Build local image
docker build -t myapp:v1 .

# Load image into kind cluster
kind load docker-image myapp:v1 --name my-cluster

# For images from archive
kind load image-archive myimage.tar --name my-cluster

# Verify image is available
docker exec -it my-cluster-control-plane crictl images | grep myapp

Custom Kubernetes Configuration

# custom-k8s-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
  # Enable specific feature gates
  CSIMigration: true
runtimeConfig:
  # Enable specific API versions
  "api/alpha": "true"
kubeadmConfigPatches:
  - |
    kind: ClusterConfiguration
    apiServer:
      extraArgs:
        enable-admission-plugins: NodeRestriction,PodSecurityPolicy
nodes:
  - role: control-plane
    kubeadmConfigPatches:
      - |
        kind: InitConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            max-pods: "150"

Networking Configuration

# networking-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  # Disable default CNI (install your own)
  disableDefaultCNI: true
  # Change pod subnet
  podSubnet: "10.244.0.0/16"
  # Change service subnet
  serviceSubnet: "10.96.0.0/12"
  # API server address
  apiServerAddress: "127.0.0.1"
  # API server port
  apiServerPort: 6443

Install Custom CNI

# calico-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  disableDefaultCNI: true
  podSubnet: "192.168.0.0/16"
nodes:
  - role: control-plane
  - role: worker
# Create cluster
kind create cluster --config calico-config.yaml

# Install Calico
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml

# Wait for Calico
kubectl wait --for=condition=ready pod -l k8s-app=calico-node -n kube-system --timeout=90s

Local Registry

#!/bin/bash
# create-cluster-with-registry.sh

# Create registry container
reg_name='kind-registry'
reg_port='5001'

if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then
  docker run -d --restart=always -p "127.0.0.1:${reg_port}:5000" --name "${reg_name}" registry:2
fi

# Create kind cluster with registry config
cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:${reg_port}"]
    endpoint = ["http://${reg_name}:5000"]
EOF

# Connect registry to kind network
if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${reg_name}")" = 'null' ]; then
  docker network connect "kind" "${reg_name}"
fi

# Document the local registry
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: local-registry-hosting
  namespace: kube-public
data:
  localRegistryHosting.v1: |
    host: "localhost:${reg_port}"
    help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF
# Use the local registry
docker tag myapp:v1 localhost:5001/myapp:v1
docker push localhost:5001/myapp:v1

# Deploy using local registry
kubectl create deployment myapp --image=localhost:5001/myapp:v1

Delete Cluster

# Delete specific cluster
kind delete cluster --name my-cluster

# Delete default cluster
kind delete cluster

# Delete all clusters
kind delete clusters --all

CI/CD Usage

# GitHub Actions example
name: Test
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Create kind cluster
        uses: helm/kind-action@v1.8.0
        with:
          cluster_name: test-cluster
      
      - name: Test cluster
        run: |
          kubectl cluster-info
          kubectl get nodes
      
      - name: Run tests
        run: |
          kubectl apply -f manifests/
          kubectl wait --for=condition=ready pod -l app=myapp --timeout=60s

Troubleshooting

# Get cluster logs
kind export logs --name my-cluster ./logs

# Check Docker containers
docker ps -a | grep kind

# Access node shell
docker exec -it my-cluster-control-plane bash

# Check kubelet logs on node
docker exec -it my-cluster-control-plane journalctl -u kubelet

# Restart cluster (delete and recreate)
kind delete cluster --name my-cluster
kind create cluster --name my-cluster --config config.yaml

Common Issues

# Port already in use
# Check what's using the port
lsof -i :80
# Change port mapping in config

# Image not found
# Load image into cluster
kind load docker-image myapp:v1

# Network issues
# Restart Docker
docker restart

# Insufficient resources
# Increase Docker memory/CPU limits
# Docker Desktop > Settings > Resources

Summary

kind creates disposable Kubernetes clusters in Docker containers for local development and testing. Use configuration files to set up multi-node clusters, expose ports for ingress, and mount host directories. Load local Docker images with kind load docker-image. kind is ideal for CI/CD pipelines due to its fast creation time and isolation. Remember to delete clusters when done to free resources.


📘 Go Further with Kubernetes Recipes

Love this recipe? There’s so much more! This is just one of 100+ hands-on recipes in our comprehensive Kubernetes Recipes book.

Inside the book, you’ll master:

  • ✅ Production-ready deployment strategies
  • ✅ Advanced networking and security patterns
  • ✅ Observability, monitoring, and troubleshooting
  • ✅ Real-world best practices from industry experts

“The practical, recipe-based approach made complex Kubernetes concepts finally click for me.”

👉 Get Your Copy Now — Start building production-grade Kubernetes skills today!

#kind #local-development #docker #testing #development
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens