On a remote cluster, every change means building an image, pushing it to a registry, deploying it and waiting for the pod. When I work on network policies, resource limits or a pod that does not start, I repeat that cycle many times, and each cycle takes minutes.
Minikube runs a Kubernetes cluster on the local machine. It has the same API server, scheduler and RBAC model as a real cluster. You can run several clusters at the same time as named profiles, each with its own CPU and memory limits.
This note covers the installation on a Mac, profiles, kubectl contexts, a separate kubeconfig per cluster, and k9s.
Installing Minikube
Install it with Homebrew:
brew install minikube
Check the installation:
minikube version
Choosing a driver
Minikube needs a driver to run the cluster in a VM or a container. On a Mac, two drivers work well:
Docker: if Docker Desktop is running, Minikube finds it and needs no extra setup.
QEMU: it uses fewer resources than Docker Desktop and runs natively on Intel and Apple Silicon:
brew install qemu
If Docker Desktop is already running, use it. On my Apple Silicon machines QEMU uses less memory and starts faster, so I use QEMU there. On Intel I did not see a real difference. Set the driver in the configuration, so Minikube does not choose it again at every start:
minikube config set driver docker
# or
minikube config set driver qemu2
Starting a cluster
minikube start
This creates a cluster named minikube, the default profile, with 2 CPUs and 2 GB of RAM. For workloads closer to production, give it more resources:
minikube start --cpus 4 --memory 8192
Memory is in MB, so 8192 is 8 GB. When the cluster is running, check it:
minikube status
kubectl cluster-info
kubectl get nodes
The output shows one node in the Ready state.
Multiple clusters with profiles
A profile is a separate Minikube cluster. Each profile has its own VM or container, its own Kubernetes state and its own network. Profiles can run at the same time, so I keep one cluster per project and switch between them without deleting anything.
Creating named clusters
# A resource-intensive microservices project
minikube start -p project-a --cpus 4 --memory 8192
# A lighter stack, a database and a couple of APIs
minikube start -p project-b --cpus 2 --memory 4096
List all clusters:
minikube profile list
|-----------|--------|---------|--------------|------|---------|---------|-------|--------|
| Profile | VM | Runtime | IP | Port | Version | Status | Nodes | Active |
|-----------|--------|---------|--------------|------|---------|---------|-------|--------|
| project-a | docker | docker | 192.168.49.2 | 8443 | v1.31.0 | Running | 1 | * |
| project-b | docker | docker | 192.168.49.3 | 8443 | v1.31.0 | Running | 1 | |
|-----------|--------|---------|--------------|------|---------|---------|-------|--------|
The * marks the active profile.
Cluster lifecycle
# Stop without losing state
minikube stop -p project-a
# Restart (picks up exactly where it left off)
minikube start -p project-a
# Pause: frees CPU without stopping the cluster
minikube pause -p project-b
# Unpause
minikube unpause -p project-b
# Delete completely
minikube delete -p project-b
stop keeps the cluster state, including workloads, configmaps and secrets. delete removes everything.
Switching between clusters
When Minikube creates a profile, it adds a kubectl context with the same name to ~/.kube/config. A context is a named entry with a cluster endpoint, credentials and a namespace. kubectl sends its commands to the cluster of the active context.
List and switch
# Show all available contexts
kubectl config get-contexts
# Switch globally
kubectl config use-context project-a
kubectl config use-context project-b
# Confirm the active context
kubectl config current-context
Per-command targeting
use-context changes the context for every terminal window. When I work on two projects at the same time, I use the --context flag instead. It sends one command to one cluster and does not change the active context:
kubectl get pods --context=project-a
kubectl get services --context=project-b
Per-session kubeconfig
For a long session, or for a script that must behave like the CI pipeline, export a kubeconfig file with only one cluster in it and use it in that terminal:
# Extract the kubeconfig for project-a
kubectl config view --minify --context=project-a --raw > ~/.kube/project-a.yaml
# In that terminal session only
export KUBECONFIG=~/.kube/project-a.yaml
kubectl get pods # always hits project-a, regardless of global context
kubectl in that terminal can only reach project-a, so a script cannot deploy to the wrong cluster by mistake.
k9s
k9s is a terminal UI for Kubernetes. It shows pods, deployments, services, events and logs, and it updates them live. One key streams the logs of a pod, another key opens a shell in a container, and you can filter any list while it updates. I use k9s for most daily work and kubectl mainly in scripts.
Install
brew install k9s
Connect to a cluster
k9s uses the active kubectl context by default. To open another cluster without changing the active context:
# Use the active context
k9s
# Target a specific context
k9s --context project-a
# Use a standalone kubeconfig file
k9s --kubeconfig ~/.kube/project-a.yaml
Navigate
Press : and type a resource name to open the list of that resource:
:pods, all pods in the current namespace:deploy, deployments:svc, services:ns, namespaces (select to switch):cm, configmaps:secret, secrets
In any list, with the cursor on a row:
l, stream logs from the selected pods, exec a shell into the selected podd, describe (equivalent tokubectl describe)e, edit in$EDITORctrl+d, delete the selected resourcectrl+k, force-kill the selected pod/, filter the current listesc, go back / close panelq, quit k9s
For debugging I mostly use three commands: :ns to change namespace, l for logs and s for a shell.
Quick reference
Cluster lifecycle
minikube startminikube start -p name --cpus N --memory Nminikube profile listminikube stop -p nameminikube start -p nameminikube pause -p nameminikube delete -p nameContext management
kubectl config get-contextskubectl config use-context namekubectl config current-contextkubectl get pods --context=namekubectl config view --minify --context=name --raw > name.yamlexport KUBECONFIG=~/.kube/name.yamlk9s shortcuts
With a local cluster you still build an image for every change. To skip the registry push, build the image directly in the Docker daemon of the cluster: run eval $(minikube -p project-a docker-env) in the terminal, then docker build. The pods of that cluster can use the image directly. Set imagePullPolicy: IfNotPresent in the deployment, so Kubernetes does not try to pull the image from a registry.