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.
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.
- Runs a real Kubernetes control plane (kube-apiserver, kube-scheduler, kube-controller-manager, etcd).
- Ships with a bundled
kubectlthat matches the cluster version. - Supports multiple container runtimes: Docker (default), containerd, CRI-O.
- Supports multiple drivers: Docker, Hyper-V, KVM2, VirtualBox, QEMU, Podman, SSH, and more.
- Over 50 built-in addons to enable production-like features with a single command.
- Works on Linux, macOS, and Windows.
How Minikube works
When you run minikube start, Minikube:
- Selects a driver (Docker, Hyper-V, KVM2, etc.) to host the cluster node.
- Downloads the Minikube ISO or base image for the chosen driver.
- Provisions a VM or container with the selected Kubernetes version.
- Bootstraps the cluster using kubeadm inside the node.
- Generates or updates your
~/.kube/configwith aminikubecontext. - Starts the Kubernetes Dashboard and storage provisioner addons by default.
minikube kubectl -- get pods without installing kubectl separately. Minikube downloads the matching version automatically. Add an alias: alias kubectl="minikube kubectl --"System requirements
| Requirement | Minimum | Recommended |
|---|---|---|
| CPU cores | 2 | 4+ |
| Free RAM | 2 GB | 8 GB |
| Free disk | 20 GB | 40 GB |
| Internet (first run) | Required | Required |
| Container/VM runtime | Docker (easiest) | Docker or KVM2/Hyper-V |
Module 2: When and Why to Use Minikube
When to use Minikube
- Learning Kubernetes — The safest, cheapest place to experiment with Pods, Deployments, Services, Ingress, RBAC, ConfigMaps, and PersistentVolumes without cloud costs.
- Day-to-day application development — Build and test containerized apps against real Kubernetes APIs locally before pushing to cloud clusters.
- Validating Kubernetes manifests — Catch YAML schema errors, resource quota issues, and scheduling problems before a cloud apply.
- Terraform and Helm IaC development — Iterate on Terraform Kubernetes provider resources or Helm chart templates locally without cloud cluster costs.
- Addon exploration — Try Ingress, metrics-server, Istio, Prometheus, or cert-manager without complex cloud setup.
- Reproducing production bugs locally — Recreate issues from staging/production in a controlled local environment.
- CI/CD pipeline prototyping — Validate pipeline manifest steps locally before committing.
- Offline environments — After initial image caching, Minikube works offline. Useful for restricted networks or travel.
When NOT to use Minikube
- Production workloads — use managed Kubernetes (EKS, AKS, GKE).
- Multi-node HA testing at scale — Minikube multi-node is useful for basic testing, but not equivalent to a real production HA cluster.
- CI pipelines that need ephemeral clusters quickly — prefer kind (Kubernetes-in-Docker) which is faster to spin up in CI runners.
- GPU workloads or specialized hardware — cloud clusters handle this better.
Why Minikube over alternatives
| Tool | Model | Best For | Addon Experience | Trade-off |
|---|---|---|---|---|
| Minikube | VM or container per profile | Local dev, learning, IaC iteration | Excellent — 50+ built-in addons | Slightly heavier than kind |
| kind | K8s nodes in Docker containers | CI/CD pipelines, fast ephemeral clusters | Manual (no built-in addons) | Less convenient for interactive dev |
| k3d | k3s (lightweight K8s) in Docker | Very fast starts, resource-constrained machines | Manual | k3s differs from upstream K8s in some edge cases |
| Docker Desktop K8s | Built-in to Docker Desktop | Quick basic cluster, Windows/macOS users | Limited | Tied to Docker Desktop license, less configurable |
| EKS/AKS/GKE | Managed cloud clusters | Staging, production | Cloud-native controllers | Billable, requires connectivity |
Key advantages of Minikube
- Zero cost to start — no cloud account or billing required.
- Real Kubernetes APIs — not a simplified subset; same API surface as cloud clusters.
- Rich addons — one command enables metrics-server, Ingress, cert-manager, Prometheus, Istio, and many more.
- Multiple profiles — run several isolated clusters simultaneously (e.g., one per project).
- Version pinning — test against specific Kubernetes versions (
--kubernetes-version=v1.32.0). - Works offline — once images are cached, cluster works without internet.
- Mount host directories — live-reload code changes into the cluster without rebuilding images.
- Active community — part of the official Kubernetes SIG-Cluster-Lifecycle.
Module 3: Installation and First Cluster
Prerequisites
Install a supported driver before Minikube. Docker is the recommended default on all platforms.
- Linux: Docker (preferred) or KVM2
- macOS: Docker (preferred) or VFkit / Hyperkit
- Windows: Docker Desktop (preferred) or Hyper-V
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
| Driver | OS | Type | Recommended when |
|---|---|---|---|
| docker | Linux, macOS, Windows | Container | Docker Desktop is already installed — easiest, most portable |
| hyperv | Windows | VM (preferred) | Windows 10/11 Pro with Hyper-V enabled; better isolation than Docker driver |
| kvm2 | Linux | VM (preferred) | Linux with KVM available; best performance and isolation on Linux |
| vfkit | macOS | VM (preferred) | Apple Silicon (M1/M2/M3) Macs; faster than Docker driver on macOS |
| virtualbox | Linux, macOS, Windows | VM | Cross-platform fallback when Docker/KVM/Hyper-V unavailable |
| none | Linux only | Bare-metal | CI environments running as root on Linux; no VM overhead |
| podman | Linux, macOS, Windows | Container (experimental) | Rootless container environments |
| ssh | Any | Remote | Remote Linux host accessible over SSH |
Module 4: Cluster Lifecycle and Configuration
Core lifecycle commands
| Task | Command | Notes |
|---|---|---|
| Start cluster | minikube start | Resumes a stopped cluster or creates new |
| Start with resources | minikube start --memory=4096 --cpus=2 | Applies on first start only; reconfigure after delete |
| Check status | minikube status | Shows host, kubelet, apiserver, kubeconfig state |
| Pause | minikube pause | Halts K8s control plane but preserves state; frees CPU |
| Unpause | minikube unpause | Resumes paused cluster instantly |
| Stop | minikube stop | Shuts down the VM/container; state is preserved |
| Delete | minikube delete | Destroys the cluster and all data |
| Delete all clusters | minikube delete --all | Removes all profiles |
| Launch dashboard | minikube dashboard | Opens Kubernetes Dashboard in browser |
| SSH into node | minikube ssh | Shell access inside the Minikube node |
| Get node IP | minikube ip | Returns the cluster node's IP address |
| View logs | minikube logs | Cluster-level logs for debugging start failures |
| Update check | minikube update-check | Show 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
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
| Addon | What it provides | Verify command |
|---|---|---|
metrics-server | Resource metrics (CPU/memory) for kubectl top and HPA | kubectl top nodes |
ingress | NGINX Ingress Controller for routing HTTP/HTTPS traffic | kubectl get pods -n ingress-nginx |
ingress-dns | Local DNS resolution for Ingress hostnames (*.test) | nslookup hello.test $(minikube ip) |
dashboard | Kubernetes Dashboard Web UI | minikube dashboard |
registry | In-cluster Docker registry on port 5000 | kubectl get svc -n kube-system registry |
storage-provisioner | Auto-provisions PersistentVolumes (enabled by default) | kubectl get storageclass |
default-storageclass | Sets a default StorageClass (enabled by default) | kubectl get sc |
cert-manager | Certificate management and TLS automation | kubectl get pods -n cert-manager |
istio | Istio service mesh | kubectl get pods -n istio-system |
metallb | Bare-metal LoadBalancer implementation | kubectl get pods -n metallb-system |
efk | Elasticsearch + Fluentd + Kibana logging stack | kubectl get pods -n kube-system |
gcp-auth | Automated GCP credential injection | kubectl get secrets -A |
headlamp | Modern 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
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
| Method | Best for | Requires |
|---|---|---|
NodePort + minikube service | Quick browser testing of a single service | Nothing extra |
minikube tunnel | Testing LoadBalancer-type services end-to-end | Separate terminal, sudo on some OS |
kubectl port-forward | Temporary access to any pod/service | kubectl |
| Ingress addon | Multi-service routing, hostname-based access | minikube 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
| Method | Runtime support | Performance | Rebuilds needed? |
|---|---|---|---|
docker-env (build inside) | Docker, containerd | Best | Yes — build in daemon |
minikube image load | All | OK | No — loads archive |
minikube image build | All | OK | No — builds in cluster |
minikube cache add | All | OK | No — caches to disk |
| Registry addon | All | OK | No — push via registry |
minikube ssh + build | All | Best | Yes — 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
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
- Activate Minikube Docker daemon:
eval $(minikube docker-env) - Edit code.
- Build image:
docker build -t my-app:local . - Restart deployment to pick up new image:
kubectl rollout restart deploy/my-app -n dev - Watch pods:
kubectl get pods -n dev -w - Test via NodePort or port-forward.
- 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
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
| Task | Command |
|---|---|
| Create / start a named profile | minikube start -p my-project |
| List all profiles | minikube profile list |
| Switch active profile | minikube profile my-project |
| Get current profile | minikube profile |
| Stop a specific profile | minikube stop -p my-project |
| Delete a specific profile | minikube delete -p my-project |
| Run addon on specific profile | minikube 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
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 message | Likely cause | Fix |
|---|---|---|
Docker is unavailable | Docker Desktop not running | Start Docker Desktop; wait for it to be ready |
Exiting due to VIRT_ENABLED: This computer does not have VT-x/AMD-v enabled | Virtualization disabled in BIOS | Enable VT-x/AMD-V in BIOS, or use --driver=docker |
Unable to pick a default driver | No supported driver installed | Install Docker Desktop or another supported driver |
Port 8443 is in use | Stale Minikube process or another service | minikube delete then restart |
Cluster stuck in Stopped | Previous unclean shutdown | minikube 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
| Resource | URL |
|---|---|
| Get Started | minikube.sigs.k8s.io/docs/start/ |
| Handbook (official) | minikube.sigs.k8s.io/docs/handbook/ |
| Command Reference | minikube.sigs.k8s.io/docs/commands/ |
| Drivers | minikube.sigs.k8s.io/docs/drivers/ |
| Addons | minikube.sigs.k8s.io/docs/handbook/addons/ |
| Pushing images | minikube.sigs.k8s.io/docs/handbook/pushing/ |
| Configuration | minikube.sigs.k8s.io/docs/handbook/config/ |
| Networking / Accessing apps | minikube.sigs.k8s.io/docs/handbook/accessing/ |
| Persistent Volumes | minikube.sigs.k8s.io/docs/handbook/persistent_volumes/ |
| Mounting filesystems | minikube.sigs.k8s.io/docs/handbook/mount/ |
| Troubleshooting | minikube.sigs.k8s.io/docs/handbook/troubleshooting/ |
| FAQ | minikube.sigs.k8s.io/docs/faq/ |
| GitHub Repository | github.com/kubernetes/minikube |
| Try in Browser | codespaces.new/kubernetes/minikube |
Related tools and ecosystem
| Resource | URL |
|---|---|
| kubectl Documentation | kubernetes.io/docs/reference/kubectl/ |
| Terraform Kubernetes Provider | registry.terraform.io — hashicorp/kubernetes |
| Terraform Helm Provider | registry.terraform.io — hashicorp/helm |
| kind (for CI) | kind.sigs.k8s.io |
| k3d (lightweight K8s) | k3d.io |
| Kubernetes Version Skew Policy | kubernetes.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:
- Validate manifests locally — catch schema and scheduling errors before cloud applies.
- Iterate on IaC — run Terraform and Helm against Minikube; promote only working configurations to cloud.
- Build and test container images — use
docker-envorminikube image loadfor a tight local feedback loop. - Explore addons — one command to Ingress, metrics, cert-manager, Istio — no cloud cluster needed.
- 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.