Minikube: Local Kubernetes and IaC Handbook
Back to handbooks index

Minikube: Local Kubernetes & IaC Handbook

The complete practitioner guide to running production-equivalent Kubernetes locally — cluster lifecycle, addons, service exposure, local image workflows, Terraform IaC validation, profiles, debugging, and more.

Beginner to Intermediate Kubernetes + Terraform Local-First DevEx April 2026
Why local-first matters: Minikube lets you run real Kubernetes APIs on your laptop — no cloud account, no billing surprises. Catch manifest errors, image issues, and Terraform mistakes in seconds instead of minutes, and ship to cloud clusters with confidence.

Module 1: What is Minikube?

Core definition

Minikube is the official local Kubernetes project under kubernetes/minikube. It provisions a fully functional single-node (or optionally multi-node) Kubernetes cluster on your machine inside a VM or container. You interact with it using the same kubectl commands and YAML manifests that target EKS, AKS, or GKE — no special local-only APIs.

How Minikube works

When you run minikube start, Minikube:

  1. Selects a driver (Docker, Hyper-V, KVM2, etc.) to host the cluster node.
  2. Downloads the Minikube ISO or base image for the chosen driver.
  3. Provisions a VM or container with the selected Kubernetes version.
  4. Bootstraps the cluster using kubeadm inside the node.
  5. Generates or updates your ~/.kube/config with a minikube context.
  6. Starts the Kubernetes Dashboard and storage provisioner addons by default.
💡
Bundled kubectl: You can run minikube kubectl -- get pods without installing kubectl separately. Minikube downloads the matching version automatically. Add an alias: alias kubectl="minikube kubectl --"

System requirements

RequirementMinimumRecommended
CPU cores24+
Free RAM2 GB8 GB
Free disk20 GB40 GB
Internet (first run)RequiredRequired
Container/VM runtimeDocker (easiest)Docker or KVM2/Hyper-V

Module 2: When and Why to Use Minikube

When to use Minikube

When NOT to use Minikube

Why Minikube over alternatives

ToolModelBest ForAddon ExperienceTrade-off
MinikubeVM or container per profileLocal dev, learning, IaC iterationExcellent — 50+ built-in addonsSlightly heavier than kind
kindK8s nodes in Docker containersCI/CD pipelines, fast ephemeral clustersManual (no built-in addons)Less convenient for interactive dev
k3dk3s (lightweight K8s) in DockerVery fast starts, resource-constrained machinesManualk3s differs from upstream K8s in some edge cases
Docker Desktop K8sBuilt-in to Docker DesktopQuick basic cluster, Windows/macOS usersLimitedTied to Docker Desktop license, less configurable
EKS/AKS/GKEManaged cloud clustersStaging, productionCloud-native controllersBillable, requires connectivity

Key advantages of Minikube

Module 3: Installation and First Cluster

Prerequisites

Install a supported driver before Minikube. Docker is the recommended default on all platforms.

Installing Minikube

# ── Linux (x86-64) ─────────────────────────────────────────
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
rm minikube-linux-amd64

# ── macOS (Homebrew) ────────────────────────────────────────
brew install minikube

# ── Windows (Chocolatey) ────────────────────────────────────
choco install minikube -y

# ── Windows (PowerShell direct download) ───────────────────
New-Item -Path 'c:\' -Name 'minikube' -ItemType Directory -Force
Invoke-WebRequest -OutFile 'c:\minikube\minikube.exe' `
  -Uri 'https://github.com/kubernetes/minikube/releases/latest/download/minikube-windows-amd64.exe' `
  -UseBasicParsing
# Add c:\minikube to PATH

# ── Windows Package Manager ─────────────────────────────────
winget install Kubernetes.minikube

Installing kubectl

# macOS
brew install kubectl

# Linux (snap)
sudo snap install kubectl --classic

# Windows (Chocolatey)
choco install kubernetes-cli -y

# Or use minikube's bundled kubectl (no install needed)
minikube kubectl -- get pods -A

# Convenience alias
alias kubectl="minikube kubectl --"

Verify installation

minikube version
kubectl version --client

Start your first cluster

# Simplest start (auto-selects best available driver)
minikube start

# Explicit driver with resource allocation
minikube start --driver=docker --memory=4096 --cpus=2

# Specific Kubernetes version
minikube start --kubernetes-version=v1.32.0

# All three together
minikube start --driver=docker --memory=4096 --cpus=2 --kubernetes-version=v1.32.0

Verify cluster is running

# Cluster status
minikube status

# All system pods should be Running
kubectl get pods -A

# Node info
kubectl get nodes -o wide

# Cluster info
kubectl cluster-info

First deployment (quick smoke test)

# Deploy a sample echo server
kubectl create deployment hello-minikube --image=kicbase/echo-server:1.0
kubectl expose deployment hello-minikube --type=NodePort --port=8080

# Wait for it to become ready
kubectl wait --for=condition=available deployment/hello-minikube --timeout=60s

# Open in browser (Minikube launches a browser window automatically)
minikube service hello-minikube

# Or get URL only
minikube service hello-minikube --url

# Clean up
kubectl delete deployment hello-minikube
kubectl delete service hello-minikube

Driver selection guide

DriverOSTypeRecommended when
dockerLinux, macOS, WindowsContainerDocker Desktop is already installed — easiest, most portable
hypervWindowsVM (preferred)Windows 10/11 Pro with Hyper-V enabled; better isolation than Docker driver
kvm2LinuxVM (preferred)Linux with KVM available; best performance and isolation on Linux
vfkitmacOSVM (preferred)Apple Silicon (M1/M2/M3) Macs; faster than Docker driver on macOS
virtualboxLinux, macOS, WindowsVMCross-platform fallback when Docker/KVM/Hyper-V unavailable
noneLinux onlyBare-metalCI environments running as root on Linux; no VM overhead
podmanLinux, macOS, WindowsContainer (experimental)Rootless container environments
sshAnyRemoteRemote Linux host accessible over SSH
Windows tip: Run PowerShell or terminal as Administrator when starting Minikube for the first time. The Docker driver on Windows requires Docker Desktop with WSL 2 backend enabled.

Module 4: Cluster Lifecycle and Configuration

Core lifecycle commands

TaskCommandNotes
Start clusterminikube startResumes a stopped cluster or creates new
Start with resourcesminikube start --memory=4096 --cpus=2Applies on first start only; reconfigure after delete
Check statusminikube statusShows host, kubelet, apiserver, kubeconfig state
Pauseminikube pauseHalts K8s control plane but preserves state; frees CPU
Unpauseminikube unpauseResumes paused cluster instantly
Stopminikube stopShuts down the VM/container; state is preserved
Deleteminikube deleteDestroys the cluster and all data
Delete all clustersminikube delete --allRemoves all profiles
Launch dashboardminikube dashboardOpens Kubernetes Dashboard in browser
SSH into nodeminikube sshShell access inside the Minikube node
Get node IPminikube ipReturns the cluster node's IP address
View logsminikube logsCluster-level logs for debugging start failures
Update checkminikube update-checkShow current vs latest available Minikube version

Persistent configuration

Avoid repeating flags on every start by setting persistent defaults with minikube config set. These apply across all new profiles.

# Set default driver
minikube config set driver docker

# Set default memory and CPUs
minikube config set memory 4096
minikube config set cpus 2

# Set a default Kubernetes version
minikube config set kubernetes-version v1.32.0

# View all persistent settings
minikube config view

# Unset a value
minikube config unset memory

Selecting a Kubernetes version

Minikube ships with the latest stable Kubernetes by default. You can pin to an older or newer version to replicate specific environments.

# Pin to a specific version
minikube start --kubernetes-version=v1.32.0

# Simulate an older environment
minikube start -p legacy-cluster --kubernetes-version=v1.28.0

# Always get supported versions from official constants.go or:
minikube start --kubernetes-version=latest
Minikube supports the last three minor Kubernetes releases in line with the official Kubernetes Version Skew Support Policy. Older versions may still work but are not guaranteed.

Advanced start flags

# Choose container runtime (default is docker)
minikube start --container-runtime=containerd

# Enable Kubernetes feature gates
minikube start --feature-gates=ServerSideApply=true

# Override kubeadm or kubelet config
minikube start --extra-config=kubelet.max-pods=200
minikube start --extra-config=apiserver.v=10

# Mount a host directory into the cluster
minikube start --mount --mount-string="/host/path:/mnt/data"

# Specify disk size
minikube start --disk-size=50g

# Pre-cache container images for offline use
minikube start --cache-images=true

Environment variables

Every minikube config flag can also be set via environment variable with the MINIKUBE_ prefix.

# Disable emoji/colors in output (useful for CI)
$env:MINIKUBE_IN_STYLE = "false"

# Set custom home for minikube state files
$env:MINIKUBE_HOME = "D:\minikube-data"

Module 5: Addons — One Command to Production-Like Features

What are addons?

Minikube addons are maintained extensions that install production-equivalent cluster components with a single command. They are managed independently of the cluster and survive minikube stop/start cycles.

# List all available addons with their status
minikube addons list

# Enable an addon
minikube addons enable <name>

# Disable an addon
minikube addons disable <name>

# Enable addon for a specific profile
minikube addons enable metrics-server -p my-cluster

Essential addons reference

AddonWhat it providesVerify command
metrics-serverResource metrics (CPU/memory) for kubectl top and HPAkubectl top nodes
ingressNGINX Ingress Controller for routing HTTP/HTTPS traffickubectl get pods -n ingress-nginx
ingress-dnsLocal DNS resolution for Ingress hostnames (*.test)nslookup hello.test $(minikube ip)
dashboardKubernetes Dashboard Web UIminikube dashboard
registryIn-cluster Docker registry on port 5000kubectl get svc -n kube-system registry
storage-provisionerAuto-provisions PersistentVolumes (enabled by default)kubectl get storageclass
default-storageclassSets a default StorageClass (enabled by default)kubectl get sc
cert-managerCertificate management and TLS automationkubectl get pods -n cert-manager
istioIstio service meshkubectl get pods -n istio-system
metallbBare-metal LoadBalancer implementationkubectl get pods -n metallb-system
efkElasticsearch + Fluentd + Kibana logging stackkubectl get pods -n kube-system
gcp-authAutomated GCP credential injectionkubectl get secrets -A
headlampModern Kubernetes UI (alternative to dashboard)minikube service headlamp -n headlamp

Common addon workflows

# Enable metrics and ingress in one pass
minikube addons enable metrics-server
minikube addons enable ingress

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

# Enable local DNS for ingress
minikube addons enable ingress-dns

# Use kubectl top after enabling metrics-server
kubectl top nodes
kubectl top pods -A

# Enable in-cluster registry
minikube addons enable registry
kubectl get svc -n kube-system registry

Using Ingress with a real hostname

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  namespace: dev
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: my-app.test
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app-service
                port:
                  number: 80
# Get Minikube IP for /etc/hosts entry
minikube ip
# 192.168.49.2

# Add to /etc/hosts (Linux/macOS) or C:\Windows\System32\drivers\etc\hosts (Windows)
# 192.168.49.2  my-app.test

# Apply ingress
kubectl apply -f ingress.yaml

# Verify
kubectl get ingress -n dev
curl http://my-app.test

Module 6: Networking and Exposing Services

The localhost problem

Kubernetes Services of type LoadBalancer stay in <Pending> on local clusters because there is no cloud load balancer controller. You have three main strategies to access services locally.

Strategy 1 — NodePort (fastest for dev)

NodePort exposes a service on a static port on the node. Minikube provides a convenience command to open it instantly.

# Expose a deployment as NodePort
kubectl expose deployment my-app --type=NodePort --port=8080

# Open directly in browser
minikube service my-app

# Get URL without opening browser
minikube service my-app --url

# List all service URLs
minikube service list
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
  namespace: dev
spec:
  type: NodePort
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080  # valid range: 30000–32767

Strategy 2 — minikube tunnel (LoadBalancer simulation)

minikube tunnel runs a network route from your host into the cluster and assigns real external IPs to LoadBalancer services. Requires a separate terminal (or run as a background service).

# Run in a dedicated terminal — requires sudo/admin on some systems
minikube tunnel

# In another terminal, verify external IP is assigned
kubectl get svc my-app-service
# NAME              TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
# my-app-service    LoadBalancer   10.96.100.200   127.0.0.1     80/TCP    2m
Cleanup: Always stop the tunnel process when done (Ctrl+C). Orphaned tunnel processes can cause port conflicts. Run minikube tunnel --cleanup if you encounter stale tunnel state.

Strategy 3 — kubectl port-forward (one-off access)

For quick ad-hoc access to a specific pod or service without modifying service type.

# Forward a service to localhost
kubectl port-forward service/my-app-service 8080:80 -n dev

# Forward directly to a pod
kubectl port-forward pod/my-app-pod-xyz 8080:8080 -n dev

# Access at http://localhost:8080 while the above command runs

Strategy 4 — Ingress (production-like routing)

Enable the Ingress addon and route traffic via hostname-based rules — closest to production Ingress behavior. See Module 5 for the full Ingress workflow.

Networking summary

MethodBest forRequires
NodePort + minikube serviceQuick browser testing of a single serviceNothing extra
minikube tunnelTesting LoadBalancer-type services end-to-endSeparate terminal, sudo on some OS
kubectl port-forwardTemporary access to any pod/servicekubectl
Ingress addonMulti-service routing, hostname-based accessminikube addons enable ingress

Accessing the host from inside the cluster

To reach a service running on your host machine from inside the cluster (e.g., a local database), use the special host.minikube.internal DNS name.

# From inside a pod — reaches a service on the host at port 5432
curl http://host.minikube.internal:5432

# Verify the host IP Minikube maps
minikube ssh "cat /etc/hosts | grep host.minikube.internal"

Module 7: Managing Local Docker Images

The daemon isolation problem

Minikube runs its own container daemon inside the VM or container. Images you build on your host are not automatically visible to the cluster. There are multiple solutions — choose based on your container runtime.

Image push method comparison

MethodRuntime supportPerformanceRebuilds needed?
docker-env (build inside)Docker, containerdBestYes — build in daemon
minikube image loadAllOKNo — loads archive
minikube image buildAllOKNo — builds in cluster
minikube cache addAllOKNo — caches to disk
Registry addonAllOKNo — push via registry
minikube ssh + buildAllBestYes — inside node

Method 1 — docker-env (recommended)

Point your terminal's Docker CLI at the Minikube-internal Docker daemon. Anything you build goes directly into the cluster's image store.

# ── Linux / macOS ────────────────────────────────────────────
eval $(minikube docker-env)

# ── PowerShell (Windows) ─────────────────────────────────────
& minikube -p minikube docker-env --shell powershell | Invoke-Expression

# ── CMD (Windows) ────────────────────────────────────────────
@FOR /f "tokens=*" %i IN ('minikube -p minikube docker-env --shell cmd') DO @%i

# Verify: MINIKUBE_ACTIVE_DOCKERD should be set
echo $env:MINIKUBE_ACTIVE_DOCKERD

# Build your image — it goes into the cluster, not your host
docker build -t my-app:local .

# Verify image is visible inside cluster
docker images | grep my-app

# Revert to host Docker daemon when done
eval $(minikube docker-env -u)   # Linux/macOS
💡
docker-env is session-scoped. It only applies to the current terminal. New terminals still point at your host Docker. Re-run the eval command after minikube start if the cluster was restarted.

Method 2 — minikube image load (no rebuild)

Build on your host as normal, then load the image archive directly into the cluster. Good when you already have a built image.

# Build on host normally
docker build -t my-app:local .

# Load into Minikube cluster
minikube image load my-app:local

# Verify it's inside the cluster
minikube image ls | grep my-app

Method 3 — minikube image build

Build the image directly inside the cluster's container runtime without activating docker-env.

minikube image build -t my-app:local .

# Verify
minikube image ls | grep my-app

Method 4 — minikube cache

Cache images from a registry to disk. Minikube auto-loads cached images on every cluster start — useful for frequently used base images.

# Cache an image from registry
minikube cache add nginx:1.27
minikube cache add postgres:16

# Force reload all cached images
minikube cache reload

# List cached images
minikube cache list

# Remove from cache
minikube cache delete nginx:1.27

Deployment manifest for local images

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: dev
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: my-app:local
          imagePullPolicy: IfNotPresent  # REQUIRED for local images
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "256Mi"
# Deploy and watch
kubectl apply -f deployment.yaml
kubectl rollout status deployment/my-app -n dev

# Live logs
kubectl logs -n dev -f deploy/my-app

# Shell into running container
kubectl exec -it deploy/my-app -n dev -- sh

The complete local dev loop

  1. Activate Minikube Docker daemon: eval $(minikube docker-env)
  2. Edit code.
  3. Build image: docker build -t my-app:local .
  4. Restart deployment to pick up new image: kubectl rollout restart deploy/my-app -n dev
  5. Watch pods: kubectl get pods -n dev -w
  6. Test via NodePort or port-forward.
  7. Iterate. When satisfied, tag and push to a real registry for cloud promotion.

Mounting host directories (live reload)

For interpreted languages (Python, Node.js) you can mount your source directory directly into the cluster and reload without rebuilding.

# Mount in a background terminal
minikube mount /local/src:/mnt/src

# Reference in your Deployment
volumes:
  - name: src
    hostPath:
      path: /mnt/src
containers:
  - name: my-app
    volumeMounts:
      - name: src
        mountPath: /app/src

Module 8: Infrastructure as Code with Terraform

Why validate IaC locally?

Running Terraform against Minikube before targeting cloud clusters catches provider version mismatches, resource schema errors, RBAC mistakes, and misconfigured Helm values — all at zero cloud cost. The Terraform Kubernetes provider works identically against Minikube and EKS/AKS/GKE, so anything that passes locally has a high probability of passing in cloud.

Project layout

infra/
├── main.tf          # Provider config, namespace, deployments
├── variables.tf     # kube_context, environment, replica counts
├── outputs.tf       # Service URLs, namespace names
└── terraform.tfvars # Local overrides (gitignored)

variables.tf

variable "kube_context" {
  description = "Kubeconfig context. Use 'minikube' locally, cluster ARN/name for cloud."
  type        = string
  default     = "minikube"
}

variable "environment" {
  description = "Environment label (local, dev, staging, prod)"
  type        = string
  default     = "local"
}

variable "app_replicas" {
  description = "Number of application replicas"
  type        = number
  default     = 2
}

main.tf

terraform {
  required_version = ">= 1.6.0"

  required_providers {
    kubernetes = {
      source  = "hashicorp/kubernetes"
      version = "~> 2.30"
    }
    helm = {
      source  = "hashicorp/helm"
      version = "~> 2.13"
    }
  }
}

provider "kubernetes" {
  config_path    = pathexpand("~/.kube/config")
  config_context = var.kube_context
}

provider "helm" {
  kubernetes {
    config_path    = pathexpand("~/.kube/config")
    config_context = var.kube_context
  }
}

# ── Namespace ─────────────────────────────────────────────────
resource "kubernetes_namespace" "app" {
  metadata {
    name = "app-${var.environment}"
    labels = {
      environment = var.environment
      managed_by  = "terraform"
    }
  }
}

# ── ConfigMap ─────────────────────────────────────────────────
resource "kubernetes_config_map" "app_config" {
  metadata {
    name      = "app-config"
    namespace = kubernetes_namespace.app.metadata[0].name
  }

  data = {
    APP_ENV     = var.environment
    LOG_LEVEL   = "debug"
    API_BASE    = "http://api-service.${kubernetes_namespace.app.metadata[0].name}.svc.cluster.local"
  }
}

# ── Deployment ────────────────────────────────────────────────
resource "kubernetes_deployment" "nginx" {
  metadata {
    name      = "nginx"
    namespace = kubernetes_namespace.app.metadata[0].name
    labels = {
      app         = "nginx"
      environment = var.environment
    }
  }

  spec {
    replicas = var.app_replicas

    selector {
      match_labels = { app = "nginx" }
    }

    strategy {
      type = "RollingUpdate"
      rolling_update {
        max_unavailable = "1"
        max_surge       = "1"
      }
    }

    template {
      metadata {
        labels = { app = "nginx" }
      }

      spec {
        container {
          name  = "nginx"
          image = "nginx:1.27"

          port { container_port = 80 }

          env_from {
            config_map_ref { name = kubernetes_config_map.app_config.metadata[0].name }
          }

          resources {
            requests = { cpu = "100m", memory = "128Mi" }
            limits   = { cpu = "300m", memory = "256Mi" }
          }

          liveness_probe {
            http_get {
              path = "/"
              port = 80
            }
            initial_delay_seconds = 10
            period_seconds        = 15
          }

          readiness_probe {
            http_get {
              path = "/"
              port = 80
            }
            initial_delay_seconds = 5
            period_seconds        = 10
          }
        }
      }
    }
  }
}

# ── Service ───────────────────────────────────────────────────
resource "kubernetes_service" "nginx" {
  metadata {
    name      = "nginx"
    namespace = kubernetes_namespace.app.metadata[0].name
  }

  spec {
    selector = { app = "nginx" }

    port {
      port        = 80
      target_port = 80
      node_port   = 30080
    }

    type = "NodePort"
  }
}

outputs.tf

output "namespace" {
  value = kubernetes_namespace.app.metadata[0].name
}

output "service_name" {
  value = kubernetes_service.nginx.metadata[0].name
}

Workflow: local validate → cloud promote

# 1. Start Minikube
minikube start --driver=docker --memory=4096 --cpus=2

# 2. Init and plan locally
cd infra
terraform init
terraform plan -var="kube_context=minikube" -var="environment=local"

# 3. Apply and verify
terraform apply -var="kube_context=minikube" -var="environment=local" -auto-approve
kubectl get ns
kubectl get deploy,svc,configmap -n app-local
minikube service nginx -n app-local

# 4. Promote to cloud (change only the context variable)
terraform apply -var="kube_context=arn:aws:eks:us-east-1:123:cluster/my-eks" \
                -var="environment=dev"

# 5. Local cleanup
terraform destroy -var="kube_context=minikube" -var="environment=local" -auto-approve
💡
Cost saver: The local Terraform loop removes many broken cloud applies. A single minikube start + terraform apply cycle takes minutes and costs nothing. Running the same broken IaC against EKS can waste 10–30 minutes plus compute costs.

Deploying a Helm chart with Terraform locally

resource "helm_release" "prometheus" {
  name             = "prometheus"
  repository       = "https://prometheus-community.github.io/helm-charts"
  chart            = "kube-prometheus-stack"
  namespace        = "monitoring"
  create_namespace = true
  version          = "58.0.0"

  set {
    name  = "grafana.enabled"
    value = "true"
  }

  set {
    name  = "alertmanager.enabled"
    value = "false"  # disable for local to save memory
  }
}

# After apply, access Grafana
# kubectl port-forward svc/prometheus-grafana 3000:80 -n monitoring

Module 9: Profiles — Multiple Isolated Clusters

What are profiles?

Each Minikube profile is a completely independent cluster with its own kubeconfig context, resources, addons, and Kubernetes version. Use profiles to keep projects isolated or test different K8s versions simultaneously — all on the same machine.

Profile commands

TaskCommand
Create / start a named profileminikube start -p my-project
List all profilesminikube profile list
Switch active profileminikube profile my-project
Get current profileminikube profile
Stop a specific profileminikube stop -p my-project
Delete a specific profileminikube delete -p my-project
Run addon on specific profileminikube addons enable ingress -p my-project

Practical multi-profile setup

# Project A — current stable K8s
minikube start -p project-a --driver=docker --memory=4096 --cpus=2
minikube addons enable ingress -p project-a

# Project B — test older K8s version
minikube start -p project-b --driver=docker --memory=2048 --cpus=2 \
               --kubernetes-version=v1.28.0

# List both
minikube profile list

# Switch kubectl context to project-a
kubectl config use-context project-a

# Or use minikube profile switch
minikube profile project-a
kubectl get nodes  # operates on project-a

minikube profile project-b
kubectl get nodes  # operates on project-b

Multi-node clusters

Minikube supports adding worker nodes to a cluster — useful for testing pod scheduling, affinity rules, and DaemonSet behavior across nodes.

# Start with 2 nodes (1 control plane + 1 worker)
minikube start --nodes=2 -p multi-node

# Add a node to an existing cluster
minikube node add -p multi-node

# List nodes
minikube node list -p multi-node
kubectl get nodes

# Delete a specific node
minikube node delete multi-node-m02 -p multi-node
Each Minikube profile gets its own kubeconfig context automatically. The context name matches the profile name. Use kubectl config get-contexts to see all contexts.

Module 10: Pitfalls, Anti-Patterns, and Debugging

1) Insufficient resources — CrashLoopBackOff and Pending pods

Default Minikube resources (2 GB RAM, 2 CPUs) are too small for stacks like Prometheus, Kafka, or databases. Symptoms: pods stay Pending or enter CrashLoopBackOff immediately.

# Diagnose resource pressure
kubectl describe node minikube | grep -A10 "Conditions:"
kubectl describe node minikube | grep -A20 "Allocated resources"
kubectl top nodes   # requires metrics-server addon
kubectl top pods -A

# Fix: recreate with more resources
minikube delete
minikube start --driver=docker --memory=8192 --cpus=4

# Or set as default for all future starts
minikube config set memory 8192
minikube config set cpus 4

2) Wrong imagePullPolicy for local images

If imagePullPolicy: Always is set and the image was never pushed to a registry, Kubernetes tries to pull from Docker Hub, fails, and returns ErrImagePull or ImagePullBackOff.

# BAD — always tries to pull, fails for local-only images
containers:
  - image: my-app:local
    imagePullPolicy: Always

# GOOD — use local image if present
containers:
  - image: my-app:local
    imagePullPolicy: IfNotPresent

# Also valid — never pull, always use local
containers:
  - image: my-app:local
    imagePullPolicy: Never
# Debug pull failures
kubectl describe pod <pod-name> -n dev
kubectl get events -n dev --sort-by=.metadata.creationTimestamp

3) docker-env not activated — images not found

Building images in the host Docker daemon (not Minikube's) causes ErrImageNeverPull or ImagePullBackOff even with IfNotPresent, because the cluster daemon has no record of the image.

# Verify: MINIKUBE_ACTIVE_DOCKERD should be set
echo $env:MINIKUBE_ACTIVE_DOCKERD   # PowerShell
echo $MINIKUBE_ACTIVE_DOCKERD       # bash

# If empty — activate and rebuild
eval $(minikube docker-env)
docker build -t my-app:local .

# Or load an existing host image into the cluster
minikube image load my-app:local

4) Hardcoded Kubernetes context in Terraform

Hardcoding config_context = "minikube" prevents reuse of the same Terraform code for staging or production contexts.

# BAD — not portable
provider "kubernetes" {
  config_path    = pathexpand("~/.kube/config")
  config_context = "minikube"
}

# GOOD — variable-driven, portable across environments
variable "kube_context" {
  type    = string
  default = "minikube"
}

provider "kubernetes" {
  config_path    = pathexpand("~/.kube/config")
  config_context = var.kube_context
}

5) Forgetting to delete the tunnel process

Orphaned minikube tunnel processes hold ports open and cause conflicts when restarting services. Always stop the tunnel when done.

# Check for orphaned tunnel processes
Get-Process -Name "minikube" | Where-Object { $_.CommandLine -match "tunnel" }  # PowerShell
ps aux | grep "minikube tunnel"  # Linux/macOS

# Clean up stale routes
minikube tunnel --cleanup

6) Minikube start fails — common causes

Error messageLikely causeFix
Docker is unavailableDocker Desktop not runningStart Docker Desktop; wait for it to be ready
Exiting due to VIRT_ENABLED: This computer does not have VT-x/AMD-v enabledVirtualization disabled in BIOSEnable VT-x/AMD-V in BIOS, or use --driver=docker
Unable to pick a default driverNo supported driver installedInstall Docker Desktop or another supported driver
Port 8443 is in useStale Minikube process or another serviceminikube delete then restart
Cluster stuck in StoppedPrevious unclean shutdownminikube delete then minikube start
# General troubleshooting flow
minikube logs --follow          # Stream Minikube logs
minikube logs > minikube.log    # Save to file for inspection
minikube ssh                    # Enter the node for deeper investigation

# Force delete and start fresh
minikube delete --purge
minikube start --driver=docker

7) Pods pending due to taints on single-node

In a single-node cluster, control-plane taints may prevent certain workloads from scheduling. Remove or tolerate the taint.

# Check taints on the node
kubectl describe node minikube | grep Taint

# Remove control-plane taint to schedule user workloads
kubectl taint nodes minikube node-role.kubernetes.io/control-plane:NoSchedule-

8) Persistent volume data lost after delete

minikube delete destroys the VM and all PersistentVolume data. Stop (minikube stop) to preserve state; delete only when you intend a full reset.

# Safe operations that preserve data
minikube stop      # Shuts down, state preserved
minikube pause     # Pauses, state preserved, saves CPU

# Destructive operation
minikube delete    # DESTROYS everything — use intentionally

Quick Reference Cheat Sheet

Installation

# macOS
brew install minikube kubectl

# Windows (Chocolatey)
choco install minikube kubernetes-cli -y

# Windows (winget)
winget install Kubernetes.minikube

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

Cluster lifecycle

minikube start                                        # Auto-detect best driver
minikube start --driver=docker --memory=4096 --cpus=2 # Explicit resources
minikube start --kubernetes-version=v1.32.0           # Pin K8s version
minikube status                                       # Check cluster state
minikube pause                                        # Pause (save CPU)
minikube unpause                                      # Resume
minikube stop                                         # Stop (preserve state)
minikube delete                                       # Destroy cluster
minikube delete --all                                 # Destroy all profiles
minikube ip                                           # Cluster node IP
minikube ssh                                          # SSH into node
minikube logs                                         # Debug logs
minikube dashboard                                    # Web UI

Configuration

minikube config set driver docker
minikube config set memory 4096
minikube config set cpus 2
minikube config view
minikube update-check

Addons

minikube addons list
minikube addons enable metrics-server
minikube addons enable ingress
minikube addons enable ingress-dns
minikube addons enable registry
minikube addons enable cert-manager
minikube addons disable <name>

Networking

minikube service <name>          # Open service in browser
minikube service <name> --url   # Get URL only
minikube service list            # All service URLs
minikube tunnel                  # LoadBalancer IP simulation
minikube tunnel --cleanup        # Remove stale routes

Local images

# Method 1: docker-env (recommended)
eval $(minikube docker-env)                                    # Linux/macOS
& minikube -p minikube docker-env --shell powershell | Invoke-Expression  # PowerShell
docker build -t my-app:local .

# Method 2: load from host
minikube image load my-app:local

# Method 3: build inside cluster
minikube image build -t my-app:local .

# List images in cluster
minikube image ls

# Cache management
minikube cache add nginx:1.27
minikube cache reload
minikube cache list

Profiles (multi-cluster)

minikube start -p my-project              # Create named profile
minikube profile list                     # List all profiles
minikube profile my-project               # Switch active profile
minikube stop -p my-project               # Stop specific profile
minikube delete -p my-project             # Delete specific profile
minikube start --nodes=2 -p multi-node    # Multi-node cluster

Terraform IaC workflow

minikube start --driver=docker --memory=4096 --cpus=2

terraform init
terraform plan  -var="kube_context=minikube"
terraform apply -var="kube_context=minikube" -auto-approve

kubectl get ns,deploy,svc

terraform destroy -var="kube_context=minikube" -auto-approve

Debugging

kubectl get pods -A                                       # All pods
kubectl describe pod <name> -n <ns>                       # Pod events
kubectl logs -f deploy/<name> -n <ns>                     # Pod logs
kubectl get events -n <ns> --sort-by=.metadata.creationTimestamp
kubectl top nodes                                         # Resource usage
kubectl top pods -A
minikube logs --follow                                    # Minikube logs

Reference Links

Official Minikube documentation

Related tools and ecosystem

ResourceURL
kubectl Documentationkubernetes.io/docs/reference/kubectl/
Terraform Kubernetes Providerregistry.terraform.io — hashicorp/kubernetes
Terraform Helm Providerregistry.terraform.io — hashicorp/helm
kind (for CI)kind.sigs.k8s.io
k3d (lightweight K8s)k3d.io
Kubernetes Version Skew Policykubernetes.io — version skew policy

Final Takeaway

Minikube gives you a production-equivalent Kubernetes cluster on your laptop at zero cost. Use it as the first gate in your delivery loop:

  1. Validate manifests locally — catch schema and scheduling errors before cloud applies.
  2. Iterate on IaC — run Terraform and Helm against Minikube; promote only working configurations to cloud.
  3. Build and test container images — use docker-env or minikube image load for a tight local feedback loop.
  4. Explore addons — one command to Ingress, metrics, cert-manager, Istio — no cloud cluster needed.
  5. Isolate with profiles — one profile per project; run multiple K8s versions side by side.

If it works in Minikube, it's ready to be promoted to cloud. That keeps cloud clusters clean, reduces debugging time, and lowers spend.

💡
Minikube vs kind: Use Minikube for local interactive development (rich addons, easy image workflow, profiles). Use kind for CI/CD pipelines where you need fast ephemeral clusters inside a Docker runner. Both are excellent tools — they complement each other.