You build an image with docker build on your laptop, kubectl apply a deployment that references it, and get ImagePullBackOff. The image is right there on your machine, but Kubernetes can't find it.
Minikube runs its own container runtime inside a VM or container, separate from the Docker daemon on your host. Your local image is invisible to the cluster unless you put it there explicitly. There are a few ways to handle this, and which one you want depends on whether you're iterating fast on a single image or just trying to get something built elsewhere into the cluster.
Option 1: Build directly inside Minikube
If you're iterating on the same image over and over, the fastest workflow is to point your local Docker CLI at Minikube's daemon and build straight into the cluster. Nothing has to be copied or transferred afterward.
Run this in your terminal:
1eval $(minikube docker-env)
The docker-env subcommand prints shell exports that point variables like DOCKER_HOST and DOCKER_TLS_VERIFY at Minikube's internal daemon. The eval wrapper applies those exports in your current shell.
To confirm it worked, check which daemon docker is now talking to:
1docker info --format '{{.Name}}'
1minikube
If the output is minikube (or your profile name), every subsequent docker command in this shell will run against the cluster's daemon. Build your image as usual:
1docker build -t myapp:0.1.0 .
That's it. The image lives in Minikube's daemon, and any deployment that references myapp:0.1.0 will run without a registry pull.
When you're done, either close the shell or unset the variables to go back to your host daemon:
1eval $(minikube docker-env -u)
A couple of things to know. This only works when Minikube is on the Docker runtime; if you started it with --container-runtime=containerd, use minikube image build instead. The image only lives inside Minikube, so a minikube delete wipes it. And each Minikube profile has its own daemon, so for multi-profile setups, target a specific one with minikube docker-env -p myprofile.
Option 2: Load a prebuilt image with minikube image load
When the image already exists in your host Docker (it was built by CI, pulled from a private registry, or whatever), there's no need to rebuild. Just transfer it:
12docker build -t myapp:0.1.0 .minikube image load myapp:0.1.0
minikube image load exports the image as a tar from your local Docker, copies it into the Minikube node, and imports it into the cluster's container runtime. This works whether Minikube is running Docker, containerd, or CRI-O underneath.
Verify the image landed:
1minikube image ls | grep myapp
1docker.io/library/myapp:0.1.0
You can pass multiple images as space-separated arguments. For multi-profile setups, use -p to target a specific cluster.
One note: minikube cache add is the older command for this. It's deprecated, and minikube image load is the replacement.
Configure the deployment to skip the registry pull
Loading the image is only half the job, and this is where most people get stuck the first time. Kubernetes defaults imagePullPolicy to Always for any image tagged :latest (or with no tag at all). That forces a registry pull, which fails because your image only exists locally. For any other tag, the default is IfNotPresent, which works fine with locally available images.
So the cleanest fix is to use a specific version tag, which is what you'd do in production anyway:
1234567891011121314151617apiVersion: apps/v1kind: Deploymentmetadata:name: myappspec:replicas: 1selector:matchLabels:app: myapptemplate:metadata:labels:app: myappspec:containers:- name: myappimage: myapp:0.1.0
No imagePullPolicy is needed; the default IfNotPresent will use the local image.
If you'd rather stay on :latest while iterating (and a lot of people do, despite the advice not to), set imagePullPolicy: Never explicitly:
12345spec:containers:- name: myappimage: myapp:latestimagePullPolicy: Never
This guarantees Kubernetes will never attempt a registry pull. The pod fails immediately with ErrImageNeverPull if the image isn't already on the node, which is actually a feature in local dev: the error message tells you exactly what went wrong.
The equivalent flow for kind
Kind is structurally different but the model is the same. Kind nodes are Docker containers running containerd, so there's no equivalent of eval $(minikube docker-env). You can't point docker build directly at a kind node.
What you can do is load a prebuilt image, just like with Minikube:
12docker build -t myapp:0.1.0 .kind load docker-image myapp:0.1.0
The command produces output like this:
1Image: "myapp:0.1.0" with ID "sha256:e7d1111901cd..." not yet present on node "kind-control-plane", loading...
Kind defaults the cluster name to kind. For anything else, use --name:
1kind load docker-image myapp:0.1.0 --name dev
For multi-node clusters, the image is loaded onto every node by default. If you know your pods only schedule onto specific nodes, you can save time with --nodes:
1kind load docker-image myapp:0.1.0 --name dev --nodes dev-worker,dev-worker2
The same imagePullPolicy rules apply: use a specific tag, or set imagePullPolicy: Never.
Common pitfalls
Mismatched CPU architectures on Apple Silicon. If you're on an M-series Mac and your Minikube cluster is running an amd64 VM (under qemu, say), an image you load from host Docker will be arm64 by default. The kubelet will fail with exec format error, which is one of those error messages that tells you nothing the first time you see it. Either start Minikube with --driver=docker so it runs natively on arm64, or build with docker build --platform=linux/amd64 before loading.
Loaded images don't survive minikube delete. Whatever you build into the daemon or load with minikube image load is gone when the cluster is destroyed. The old minikube cache add workflow used to persist images across resets in $MINIKUBE_HOME/cache/images, but minikube image load doesn't currently do that. It catches a lot of people out after the deprecation, especially in scripted workflows that assume the cache will be there on the next minikube start. For images you want to persist, your options are the local registry addon (minikube addons enable registry) or scripting the reloads.
Stale image after rebuilding with the same tag. If you rebuild myapp:0.1.0 and re-apply the same deployment, Kubernetes sees the tag hasn't changed and won't restart pods. Bump the tag every build (the right answer), or trigger a rollout manually after each rebuild:
1kubectl rollout restart deployment/myapp
When imagePullPolicy is Never or IfNotPresent, the new image will be picked up on restart. With Always and :latest, you'd also need to push to a registry, which defeats the whole point of working locally.
eval $(minikube docker-env) stuck in a forgotten shell. Set up Minikube's docker-env in one shell, move on to other work, and every subsequent docker build in that shell will quietly build into Minikube rather than your host. The confusion usually shows up as "why isn't this image on my machine?". The MINIKUBE_ACTIVE_DOCKERD environment variable is the giveaway — echo $MINIKUBE_ACTIVE_DOCKERD will tell you whether you're still pointed at Minikube.
Final thoughts
Local Kubernetes is fine for fast iteration, but the ImagePullBackOff errors that are easy to spot in a single-pod local deployment turn into something else at scale. Now it's one node out of fifty pulling from a slow mirror, or a registry that rate-limits you intermittently. The error is the same; finding it isn't.
Dash0's Kubernetes monitoring surfaces pod health and container status alongside real-time logs and distributed traces, so you can spot deployment issues before they cascade.
Start a free trial to monitor your clusters from local dev to production in one place. No credit card required.