TestForge Blog
← All Posts

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.

TestForge Team ·

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.


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

ToolCharacteristicsBest For
KindLightweight, CI-friendlySingle service, CI pipelines
SkaffoldSimple config, fast startNew projects, small teams
TiltWeb UI, fine-grained controlMicroservices, 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.