Skip to main content

Overview

Kind (Kubernetes IN Docker) is a tool for running local Kubernetes clusters using Docker containers as nodes. It’s perfect for local development, testing, and CI/CD pipelines.

Why Kind?

Fast Setup

Create a cluster in seconds without heavy virtualization

Multi-Node Support

Simulate production topologies with control-plane and worker nodes

Port Mapping

Expose services on your localhost for easy testing

CI/CD Ready

Lightweight and perfect for automated testing pipelines

Prerequisites

Before you begin, install:
  • Docker Desktop (or Docker Engine)
  • Kind CLI
    # macOS
    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 (using Chocolatey)
    choco install kind
    
  • kubectl
    # macOS
    brew install kubectl
    
    # Linux
    curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
    chmod +x kubectl
    sudo mv kubectl /usr/local/bin/
    

Cluster Configuration

Basic Cluster Setup

Create a simple single-node cluster:
kind create cluster --name local
This creates a basic cluster with one control-plane node.

Multi-Node Cluster with Port Mapping

For a production-like environment with port forwarding, create a cluster configuration file:
cluster-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30007     # need this as kind runs on docker 
    hostPort: 30007
  - containerPort: 30008
    hostPort: 30008
- role: worker
- role: worker
Port Mapping Explained:
  • containerPort: Port inside the Kind container
  • hostPort: Port exposed on your localhost
  • Maps NodePort services to your local machine for easy access
Create the cluster with this configuration:
kind create cluster --name local --config cluster-config.yaml
This creates:
  • 1 control-plane node with port mappings
  • 2 worker nodes
  • Services accessible on localhost:30007 and localhost:30008

Cluster Management

List Clusters

View all Kind clusters:
kind get clusters

Get Cluster Info

Check cluster details:
kubectl cluster-info --context kind-local

Switch Context

Kind automatically sets the kubectl context. To switch back:
kubectl config use-context kind-local
View all contexts:
kubectl config get-contexts

Delete Cluster

Remove a cluster when done:
kind delete cluster --name local

Working with Your Cluster

Verify Nodes

Check that all nodes are ready:
kubectl get nodes
Expected output:
NAME                  STATUS   ROLES           AGE   VERSION
local-control-plane   Ready    control-plane   2m    v1.27.3
local-worker          Ready    <none>          2m    v1.27.3
local-worker2         Ready    <none>          2m    v1.27.3

Deploy a Test Application

Create a simple nginx deployment:
kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --port=80 --type=NodePort --name=nginx-service
Get the NodePort:
kubectl get service nginx-service

Access Services Locally

With port mapping configured, access NodePort services directly:
# If your service uses NodePort 30007
curl http://localhost:30007
Alternatively, use port-forwarding for any service:
kubectl port-forward service/nginx-service 8080:80
Access at http://localhost:8080

Loading Local Images

One of Kind’s most powerful features is loading local Docker images without a registry.

Build and Load an Image

# Build your image
docker build -t my-app:local .

# Load it into Kind
kind load docker-image my-app:local --name local

Deploy Using Local Image

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: my-app:local
        imagePullPolicy: Never  # Important: Don't try to pull from registry
        ports:
        - containerPort: 8080
Always set imagePullPolicy: Never when using locally loaded images to prevent Kubernetes from trying to pull them from a registry.
Apply the deployment:
kubectl apply -f deployment.yaml

Advanced Configuration

Custom Kubernetes Version

Specify a Kubernetes version:
kind create cluster --name local --image kindest/node:v1.27.3

Ingress Support

Enable ingress for testing ingress controllers:
ingress-cluster.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 and install NGINX ingress:
kind create cluster --config ingress-cluster.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml

Persistent Storage

Kind supports persistent volumes using local storage:
pvc-example.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: local-storage
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

Development Workflow

Typical Development Loop

  1. Code changes → Make changes to your application
  2. Build imagedocker build -t my-app:local .
  3. Load imagekind load docker-image my-app:local --name local
  4. Restart podskubectl rollout restart deployment/my-app
  5. Test → Access via port-forward or NodePort
  6. Debugkubectl logs -f deployment/my-app

Hot Reload with Skaffold

For faster iteration, use Skaffold with Kind:
# Install Skaffold
brew install skaffold

# Run in dev mode
skaffold dev
Skaffold automatically rebuilds and redeploys on file changes.

Testing Production Scenarios

Simulate Node Failures

# Get node name
kubectl get nodes

# Drain a node (simulates maintenance)
kubectl drain local-worker --ignore-daemonsets --delete-emptydir-data

# Watch pods reschedule
kubectl get pods -o wide -w

# Uncordon the node
kubectl uncordon local-worker

Test Resource Constraints

Apply resource limits to simulate constrained environments:
resource-limits.yaml
apiVersion: v1
kind: Pod
metadata:
  name: resource-test
spec:
  containers:
  - name: app
    image: nginx
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

Troubleshooting

Cluster Won’t Start

Check Docker is running:
docker ps
Increase Docker resources (Docker Desktop → Settings → Resources):
  • CPU: At least 2 cores
  • Memory: At least 4GB

Image Pull Errors

If pods can’t pull images:
# Verify image is loaded
docker exec -it local-control-plane crictl images

# Reload if needed
kind load docker-image my-app:local --name local

Port Already in Use

If ports 30007/30008 are taken:
# Find what's using the port
lsof -i :30007

# Kill the process or use different ports in cluster-config.yaml

Reset Everything

Clean slate:
kind delete cluster --name local
docker system prune -a
kind create cluster --name local --config cluster-config.yaml

Performance Tips

Optimize Kind for Development:
  • Keep clusters small (1-2 worker nodes unless testing scaling)
  • Delete unused images: docker image prune -a
  • Use lightweight base images (Alpine Linux)
  • Disable unnecessary features in development
  • Restart Docker Desktop if performance degrades

Comparing to Production (GKE)

FeatureKind (Local)GKE (Production)
CostFreePaid
Setup TimeSecondsMinutes
Multi-nodeSimulatedTrue distributed
Load BalancersPort mappingReal LBs
Persistent StorageHost volumesCloud disks
ScalingManualAuto-scaling
Best ForDevelopmentProduction

Next Steps

Deploy to GKE

Move your app to production on GKE

ArgoCD GitOps

Set up GitOps workflow

Additional Resources

Build docs developers (and LLMs) love