How to Set Up a Kubernetes Development Environment with Minikube

How to Set Up a Kubernetes Development Environment with Minikube

Before you deploy anything to a real EKS cluster, you need somewhere cheap, fast, and disposable to experiment — that’s exactly what Minikube is for. I’ve used it for years as a local sandbox to test manifests, try out new CRDs, and break things safely before they ever touch a shared cluster. This guide walks through getting Minikube running, configuring it properly, and using it in a way that actually mirrors production patterns, so what you learn locally transfers directly to EKS.

What Is Minikube?

Minikube runs a single-node (or optionally multi-node) local Kubernetes cluster inside a VM or container on your machine. It’s not a toy — it runs the real kube-apiserver, kube-scheduler, kube-controller-manager, kubelet, and etcd, just packaged for local development. This makes it distinct from tools like kind (which is container-based and more CI-oriented) or fully managed clusters like EKS.

Installing Minikube

On macOS:

brew install minikube

On Linux:

curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube

On Windows (via Chocolatey):

choco install minikube

You’ll also need kubectl:

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --client

Choosing a Driver

Minikube supports multiple “drivers” — the underlying virtualization or container runtime it uses:

  • docker — runs Minikube as a container using Docker; simplest option on most machines
  • hyperkit / hyperv / virtualbox — full VM-based isolation
  • podman — rootless container alternative to Docker

For most developers today, Docker is the path of least resistance:

minikube start --driver=docker

Set it as the default so you don’t need the flag every time:

minikube config set driver docker

Starting Your Cluster

Basic start:

minikube start

More realistic production-mirroring start, specifying resources and a specific Kubernetes version matching your EKS clusters:

minikube start \
  --driver=docker \
  --cpus=4 \
  --memory=8192 \
  --disk-size=40g \
  --kubernetes-version=v1.29.0 \
  --nodes=2

Matching --kubernetes-version to your EKS cluster’s control plane version is genuinely important — testing manifests against the same API version you’ll deploy to avoids “works locally, breaks in prod” surprises from deprecated or graduated APIs.

Verify it’s running:

minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

Confirm kubectl is pointed at it:

kubectl config current-context
kubectl get nodes
NAME       STATUS   ROLES           AGE   VERSION
minikube   Ready    control-plane   1m    v1.29.0
minikube-m02   Ready    <none>      1m    v1.29.0

Enabling Add-ons

Minikube ships with a curated set of add-ons that mirror common cluster services:

minikube addons list

Useful ones for local development:

minikube addons enable ingress
minikube addons enable metrics-server
minikube addons enable dashboard
minikube addons enable storage-provisioner

The ingress add-on installs NGINX Ingress Controller, letting you test Ingress resources locally exactly as you would with the AWS Load Balancer Controller on EKS (the resource spec is the same; only the controller implementation differs).

Launch the dashboard:

minikube dashboard

Deploying a Sample Application

Let’s deploy something real to confirm everything works end-to-end.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-node
  labels:
    app: hello-node
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-node
  template:
    metadata:
      labels:
        app: hello-node
    spec:
      containers:
        - name: hello-node
          image: registry.k8s.io/e2e-test-images/agnhost:2.39
          args: ["netexec", "--http-port=8080"]
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              cpu: 200m
              memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
  name: hello-node
spec:
  selector:
    app: hello-node
  ports:
    - port: 80
      targetPort: 8080
  type: LoadBalancer
kubectl apply -f hello-node.yaml
kubectl get deployment hello-node
kubectl get pods -l app=hello-node

Minikube can’t actually provision a cloud load balancer, so it provides a tunnel to simulate LoadBalancer services:

minikube service hello-node

This opens the service in your browser, or prints the accessible URL, e.g. http://127.0.0.1:54021.

Using Local Images Without a Registry

One of the most useful things for local dev: building images directly inside Minikube’s Docker daemon so you don’t need to push to ECR just to test.

eval $(minikube docker-env)
docker build -t hello-node:dev .

Then reference it in your manifest with imagePullPolicy: Never so Kubernetes doesn’t try to pull from a registry:

spec:
  containers:
    - name: hello-node
      image: hello-node:dev
      imagePullPolicy: Never

Remember to eval $(minikube docker-env) in every new shell session where you want to build into Minikube’s daemon — it’s a per-shell environment change, not persistent.

Persistent Volumes Locally

Minikube’s default storage-provisioner add-on dynamically provisions hostPath-backed PersistentVolumes, letting you test PVC-based workloads (like the StatefulSets or PVC articles in this series) without needing EBS:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: standard
kubectl apply -f test-pvc.yaml
kubectl get pvc
kubectl get pv

Helm on Minikube

Minikube is also the ideal place to test Helm charts before pointing them at EKS:

curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-release bitnami/nginx --set service.type=NodePort

CI Integration

For local CI runs (e.g., testing a Helm chart or Kustomize overlay before opening a PR), you can start Minikube inside GitHub Actions:

name: local-cluster-test
on: [pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Start Minikube
        uses: medyagh/setup-minikube@latest
      - name: Deploy and Test
        run: |
          kubectl apply -f k8s/
          kubectl wait --for=condition=available deployment/hello-node --timeout=60s
          kubectl get pods

This is genuinely useful for smoke-testing manifests in CI before they ever reach a staging EKS cluster — catching syntax errors, missing resource limits, or bad selectors early and cheaply.

Multi-Node Clusters and Testing Scheduling Behavior

A single-node Minikube cluster is fine for basic manifest testing, but it can’t teach you anything about scheduling behavior — pod anti-affinity, topology spread constraints, node selectors targeting specific labeled nodes. Since Minikube supports multi-node clusters (as shown in the earlier --nodes=2 example), you can actually exercise these patterns locally:

minikube start --nodes=3 --cpus=2 --memory=4096
kubectl label node minikube-m02 workload-type=batch
kubectl label node minikube-m03 workload-type=batch
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spread-test
spec:
  replicas: 4
  selector:
    matchLabels:
      app: spread-test
  template:
    metadata:
      labels:
        app: spread-test
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: spread-test
      containers:
        - name: app
          image: nginx:1.25
kubectl apply -f spread-test.yaml
kubectl get pods -l app=spread-test -o wide

Watching pods actually distribute across minikube, minikube-m02, and minikube-m03 gives you real confidence the constraint works before it matters on a 50-node EKS cluster.

Testing Kubernetes Version Upgrades Locally

Minikube also supports multiple named cluster “profiles,” letting you run several independent clusters side by side — useful for testing an upgrade path before touching a real cluster:

minikube start -p k8s-129 --kubernetes-version=v1.29.0
minikube start -p k8s-130 --kubernetes-version=v1.30.0
kubectl config get-contexts
kubectl config use-context k8s-130

Deploy the same manifests to both profiles and diff kubectl diff output or check for deprecation warnings before scheduling an actual EKS control-plane upgrade — a cheap way to catch API deprecations early.

Simulating Ingress and TLS Locally

Beyond the basic ingress addon, you can test TLS termination behavior that mirrors an ACM-backed ALB in production:

minikube addons enable ingress
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key -out tls.crt -subj "/CN=app.local"
kubectl create secret tls app-tls --cert=tls.crt --key=tls.key
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
    - hosts: ["app.local"]
      secretName: app-tls
  rules:
    - host: app.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: hello-node
                port:
                  number: 80

Add 127.0.0.1 app.local to /etc/hosts (pointing at minikube ip if using a VM-based driver) and you have a locally working HTTPS ingress flow that exercises the same resource shapes you’ll deploy against the AWS Load Balancer Controller later.

Common Mistakes

  • Forgetting eval $(minikube docker-env) and being confused why your local image build isn’t visible to the cluster (it built into your host Docker daemon, not Minikube’s).
  • Using a Kubernetes version wildly different from your production EKS cluster, then being surprised when a manifest that worked locally hits deprecated-API errors in prod, or vice versa.
  • Under-provisioning --memory and --cpus, causing pods to get evicted or stuck pending on a resource-starved single node.
  • Forgetting minikube tunnel is required in a separate terminal for LoadBalancer-type services to get a real routable IP on some driver setups.
  • Leaving Minikube running in the background indefinitely, silently eating your laptop’s battery and RAM — minikube stop when you’re done for the day, minikube delete to fully reclaim resources.

Cleaning Up

# Pause without deleting (keeps state, frees CPU)
minikube pause

# Stop entirely
minikube stop

# Delete cluster completely
minikube delete

# Delete all profiles/clusters
minikube delete --all

Troubleshooting

# General cluster logs
minikube logs

# SSH into the Minikube VM/container for deep debugging
minikube ssh

# Check add-on status
minikube addons list

# Reset a broken cluster
minikube delete && minikube start

Best Practices

  • Pin --kubernetes-version to match your target EKS version for realistic testing.
  • Use namespaces inside Minikube just like production — don’t get lazy and dump everything into default.
  • Script your Minikube setup (start, addon enablement, seed manifests) in a Makefile or shell script so onboarding new team members takes minutes, not an afternoon of tribal knowledge.
  • Treat Minikube as a first testing gate before staging — not a replacement for a proper staging EKS cluster, since networking, IAM, storage classes, and load balancer behavior will differ meaningfully from a real cloud environment.

Summary

Minikube gives you a real, disposable, single- or multi-node Kubernetes cluster on your laptop, ideal for fast local iteration before anything touches EKS. Match its Kubernetes version to your production clusters, use the docker-env trick to skip registry pushes during development, and layer in add-ons like ingress and metrics-server to mirror production behavior as closely as possible locally. It won’t replace a real staging environment for testing cloud-specific behavior like IAM, EBS, or ALB integration, but for day-to-day manifest and application development, it’s hard to beat.

References

Total
1
Shares

Leave a Reply

Previous Post
How to Implement a Canary Release in Kubernetes

How to Implement a Canary Release in Kubernetes

Next Post
How to Manage Namespaces in Kubernetes

How to Manage Namespaces in Kubernetes

Related Posts