Kubernetes Dev & Ops in Practice 1 — Local Development Environment (Kind + Skaffold)
Set up a local Kubernetes cluster with Kind and automate your development loop with Skaffold and Tilt. A practical guide to developing in a production-equivalent environment.
Why You Need a Local Kubernetes Environment
The era of docker run being enough is over. Once your service is composed of Deployments, Services, ConfigMaps, and Ingress resources, you need to develop in that same structure locally. The goal is to eliminate the classic “works on my machine but fails in the cluster” problem by removing environment differences at the source.
1. Setting Up a Local Cluster with Kind
Kind (Kubernetes IN Docker) uses Docker containers as nodes to create a lightweight cluster. It works identically in CI environments, giving you high dev/CI consistency.
Installation
# macOS
brew install kind
# Linux
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.23.0/kind-linux-amd64
chmod +x ./kind && mv ./kind /usr/local/bin/kind
Multi-Node Cluster Configuration
# kind-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
- containerPort: 443
hostPort: 443
- role: worker
- role: worker
kind create cluster --config kind-cluster.yaml --name local
kubectl cluster-info --context kind-local
Installing an Ingress Controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=90s
2. Automating the Dev Loop with Skaffold
Skaffold automates the cycle of detecting code changes → building images → deploying to the cluster. A single skaffold dev command re-deploys on every file save.
Installation
# macOS
brew install skaffold
# Linux
curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64
chmod +x skaffold && mv skaffold /usr/local/bin
skaffold.yaml Configuration
apiVersion: skaffold/v4beta11
kind: Config
metadata:
name: my-app
build:
artifacts:
- image: my-app
docker:
dockerfile: Dockerfile
local:
push: false # no push needed for local clusters
deploy:
kubectl:
manifests:
- k8s/*.yaml
portForward:
- resourceType: service
resourceName: my-app
port: 8080
localPort: 8080
Running Development Mode
# Watch for changes and auto-deploy
skaffold dev
# Build and deploy once
skaffold run
# Clean up deployed resources
skaffold delete
3. Tilt — When You Need Finer Control
Tilt offers more flexible configuration than Skaffold and provides a web UI dashboard. It shines when you have multiple microservices and need to monitor each service’s status at a glance.
Basic Tiltfile
# Tiltfile
docker_build('my-app', '.',
live_update=[
sync('./src', '/app/src'), # Sync source files in real time
run('cd /app && npm install', # Run only on package changes
trigger=['./package.json']),
]
)
k8s_yaml(['k8s/deployment.yaml', 'k8s/service.yaml'])
k8s_resource('my-app',
port_forwards=8080,
labels=['frontend']
)
tilt up # Web UI: http://localhost:10350
tilt down # Clean up
4. Local Image Registry
Kind uses external registries by default, but connecting a local registry speeds up image push/pull significantly.
#!/bin/bash
# local-registry.sh
# Start local registry container
docker run -d --restart=always -p 5001:5000 --name registry registry:2
# Connect registry to Kind network
docker network connect kind registry
# Register registry config in Kind
kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: local-registry-hosting
namespace: kube-public
data:
localRegistryHosting.v1: |
host: "localhost:5001"
help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF
Push to localhost:5001/my-app:latest and it’s immediately available in the Kind cluster.
5. Recommended Directory Structure
my-project/
├── src/ # Application source
├── Dockerfile
├── skaffold.yaml
├── kind-cluster.yaml
└── k8s/
├── namespace.yaml
├── deployment.yaml
├── service.yaml
├── ingress.yaml
└── configmap.yaml
Keeping the k8s/ directory identical to your production structure minimizes environment drift.
6. Frequently Used Debugging Commands
# Stream Pod logs
kubectl logs -f deployment/my-app
# Shell into a Pod
kubectl exec -it deployment/my-app -- /bin/sh
# View events (find root cause)
kubectl get events --sort-by='.lastTimestamp'
# Port-forward for direct service access
kubectl port-forward svc/my-app 8080:8080
# Watch resource status in real time
watch kubectl get pods,svc,ingress
Summary
| Tool | Characteristics | Best For |
|---|---|---|
| Kind | Lightweight, CI-friendly | Single service, CI pipelines |
| Skaffold | Simple config, fast start | New projects, small teams |
| Tilt | Web UI, fine-grained control | Microservices, complex dependencies |
In the next installment, we cover Namespace and RBAC Design — how to isolate teams and environments and apply the principle of least privilege.