Why the Docker Driver on ARM64#

When running Minikube on Apple Silicon (M1/M2/M3/M4), the driver you choose determines whether your containers run natively or through emulation. The Docker driver runs containers directly on the host architecture — ARM64 — with zero emulation overhead.

This matters because QEMU user-mode emulation, which kicks in when you try to run amd64 images on ARM64, cannot reliably execute Go binaries. The specific failure is a crash in lfstack.push, deep in Go’s runtime memory management. This is not a fixable application bug — it is a fundamental incompatibility between QEMU’s user-mode emulation and Go’s lock-free stack implementation.

Starting Minikube with Docker#

minikube start --driver=docker

This creates a Kubernetes cluster where the node itself is a Docker container running on your Mac. Since Docker Desktop on Apple Silicon runs a native ARM64 Linux VM, all containers inside minikube execute as native ARM64 binaries.

To make Docker the default driver permanently:

minikube config set driver docker

Building Images into Minikube’s Docker#

Minikube runs its own Docker daemon inside the cluster node. To build images that minikube can use without pulling from a registry, point your shell at minikube’s Docker:

eval $(minikube docker-env)
docker build -t myapp:latest .

After this, docker images lists images inside the minikube node, not your host. To switch back to your host Docker:

eval $(minikube docker-env -u)

When deploying with these local images, set imagePullPolicy: Never in your pod spec so Kubernetes does not try to pull from a remote registry:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: myapp:latest
          imagePullPolicy: Never
          ports:
            - containerPort: 8080

Port Forwarding to Local Services#

Two common ways to reach services running inside minikube:

minikube service opens a tunnel and gives you a URL:

minikube service myapp --url
# http://127.0.0.1:52341

kubectl port-forward maps a local port to a pod or service:

kubectl port-forward svc/myapp 8080:8080

The minikube service approach handles NodePort services cleanly. kubectl port-forward works with any service type and gives you explicit port control.

Resource Management#

The Docker driver shares your host’s resources dynamically. Unlike the VM-based drivers (HyperKit, QEMU), which pre-allocate a fixed amount of CPU and memory at start time, the Docker driver lets containers use whatever the host has available, subject to Docker Desktop’s own resource limits.

You can still set limits at start time to cap usage:

minikube start --driver=docker --cpus=4 --memory=8192

Check what minikube thinks it has:

kubectl describe node minikube | grep -A 5 "Capacity:"

The containerd/QEMU Gotcha#

Minikube’s containerd runtime does not use Rosetta for translating amd64 images. If you deploy an image that only has an amd64 manifest, containerd falls back to QEMU user-mode emulation to run it. This is where Go binaries crash.

The fix is to always use ARM64-native images. Check an image’s architecture:

docker inspect --format='{{.Architecture}}' myimage:latest

For images that do not publish ARM64 variants (for example, Mattermost), you need to build your own from source or from ARM64 binary tarballs. There is no workaround that makes QEMU reliably run Go on ARM64 — the emulation gap is at the CPU instruction level.

Quick Reference#

Task Command
Start cluster minikube start --driver=docker
Use minikube Docker eval $(minikube docker-env)
Build local image docker build -t app:v1 .
Access service minikube service <name> --url
Check architecture docker inspect --format='{{.Architecture}}' <image>
Stop cluster minikube stop
Delete cluster minikube delete