Initializing Enclave...

How to Fix Minikube 'kubectl get nodes NotReady' After Docker Desktop Restart

Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 5–15 mins


TL;DR

  • What broke: Docker Desktop restart kills the Minikube VM socket and invalidates the internal CNI bridge, leaving the kubelet unable to reach the API server — node flips to NotReady.
  • How to fix it: Run minikube stop && minikube start or, if the Docker context is corrupt, minikube delete && minikube start --driver=docker.
  • Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your minikube status and kubectl describe node output and get a recovery script generated locally without sending data to any external server.

The Incident (What Does the Error Mean?)

You restart Docker Desktop. You open a terminal. You run:

$ kubectl get nodes
NAME       STATUS     ROLES           AGE   VERSION
minikube   NotReady   control-plane   12d   v1.28.3

Followed by:

$ minikube status
minikube
type: Control Plane
host: Stopped
kubelet: Stopped
apiserver: Stopped
kubeconfig: Configured

Immediate consequence: Your entire local Kubernetes control plane is down. kubectl apply, kubectl exec, helm install — all fail. If you're running integration tests in CI against a local cluster, every pipeline job is now blocked.

The root cause is deterministic: Docker Desktop on restart tears down and recreates the minikube Docker network bridge and the Unix domain socket that the Minikube node container depends on. The kubelet process inside the minikube container loses its CRI socket path (/var/run/docker.sock or the containerd socket), cannot re-register with the API server, and the node condition Ready flips to False — then the container itself stops.


The Attack Vector / Blast Radius

This is not a security exploit, but the blast radius in a shared dev environment or a local CI runner is severe:

  • Cascading test failures: Any GitHub Actions self-hosted runner or Jenkins agent using Minikube as the test cluster will produce false-negative failures across all PRs in flight.
  • Stale kubeconfig corruption: If Docker Desktop assigns a new internal IP to the minikube container on restart, your ~/.kube/config still points to the old cluster IP (192.168.49.2 by default). Every kubectl call silently hits a dead endpoint — no error, just a timeout, which is the worst failure mode for debugging.
  • Orphaned PVCs and PersistentVolumes: Workloads that were mid-write when Docker Desktop restarted may leave hostPath PVs in a locked state, requiring manual cleanup.
  • Multi-context contamination: If you run multiple Minikube profiles (minikube -p dev, minikube -p staging), a Docker Desktop restart can corrupt all contexts simultaneously.

How to Fix It

Basic Fix (90% of cases)

Minikube's own restart sequence handles socket re-binding:

minikube stop
minikube start

If minikube stop hangs (because the container is already dead):

docker rm -f minikube
minikube start --driver=docker

Verify recovery:

kubectl get nodes
# Expected: STATUS=Ready within 60–90 seconds
minikube status
kubectl get pods -A

Enterprise Best Practice (Persistent Clusters, CI Runners)

The core problem is that Minikube on the Docker driver is not restart-safe by default. The fix is to configure Minikube to use a static internal IP and to add a systemd-style restart policy on the container.

Step 1: Lock the Minikube container to a static IP on a dedicated bridge network.

- minikube start --driver=docker
+ minikube start --driver=docker \
+   --static-ip=192.168.200.200 \
+   --subnet=192.168.200.0/24 \
+   --memory=4096 \
+   --cpus=2 \
+   --kubernetes-version=v1.28.3

Step 2: Add a Docker restart policy to the Minikube container so it auto-recovers after Docker Desktop restart.

- # No restart policy — default is 'no'
+ docker update --restart=unless-stopped minikube

Step 3: Add a kubeconfig validation step to your shell profile or CI bootstrap script.

- kubectl get nodes
+ # Validate cluster reachability before any kubectl command
+ if ! kubectl cluster-info --request-timeout=5s > /dev/null 2>&1; then
+   echo "[ERROR] Cluster unreachable. Running minikube start..."
+   minikube start --driver=docker
+ fi
+ kubectl get nodes

Step 4: If running on macOS with Docker Desktop, ensure the Docker socket path is correct.

- DOCKER_HOST=unix:///var/run/docker.sock
+ DOCKER_HOST=unix://${HOME}/.docker/run/docker.sock
# Docker Desktop on macOS moved the socket path in v4.13+

💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

1. Liveness Probe Script in CI Bootstrap

Add this to your .github/workflows/*.yml or Jenkinsfile before any kubectl step:

#!/bin/bash
set -euo pipefail
echo "Verifying Minikube cluster health..."
for i in {1..10}; do
  STATUS=$(kubectl get nodes --no-headers 2>/dev/null | awk '{print $2}')
  if [[ "$STATUS" == "Ready" ]]; then
    echo "Cluster Ready."
    exit 0
  fi
  echo "Attempt $i: Node not ready. Waiting 10s..."
  sleep 10
done
echo "FATAL: Cluster did not become Ready. Running minikube start."
minikube start --driver=docker

2. OPA/Conftest Policy: Enforce Driver and Static IP

If your team uses a shared minikube config file (.minikube/config/config.json), enforce it with a Conftest policy:

package minikube

deny[msg] {
  input.driver != "docker"
  msg := "Minikube must use the docker driver in this environment."
}

deny[msg] {
  not input["static-ip"]
  msg := "Minikube must be started with --static-ip to survive Docker Desktop restarts."
}

3. Docker Desktop Settings

  • Enable: Settings → General → Start Docker Desktop when you log in — prevents cold-start socket races.
  • Enable: Settings → Resources → WSL Integration (Windows) or ensure the Docker socket symlink is valid on macOS after every Docker Desktop upgrade.
  • Disable: Settings → Experimental → Use containerd for pulling and storing images — this changes the CRI socket path and breaks Minikube's default assumptions without explicit --container-runtime=containerd flag.

4. Checkov / Pre-commit Hook

Add a pre-commit hook that validates your local kubeconfig before committing code that assumes cluster availability:

# .pre-commit-config.yaml
- repo: local
  hooks:
    - id: validate-kubeconfig
      name: Validate Minikube kubeconfig
      entry: bash -c 'kubectl cluster-info --request-timeout=3s'
      language: system
      pass_filenames: false

Related Diagnostics

"Part of the Performance Utility Matrix."

View all 219 Performance Tools →