on
Faster inner-loop Kubernetes: use a local registry with kind and Minikube
When you’re iterating on a service, waiting for image pushes and slow pulls is the opposite of flow. Running a local registry so your cluster can pull images directly (or letting the cluster reuse images you’ve already built) makes the inner loop feel like a live jam instead of a slow rehearsal. Below I explain the two common patterns for local development: how kind integrates with a local registry, and how Minikube provides several options (image load, registry addons, and docker-env). Practical examples and the important gotchas are included.
Why a local registry?
- Speed: push a rebuilt image to localhost and the cluster can consume it without a round-trip to Docker Hub or a cloud registry.
- Repeatability: pin tags and avoid surprising updates.
- Simplicity for CI: local registries are easy to script and reproducible in CI runners.
Kind: connect a local registry to the cluster
- The usual pattern is to run a small registry container (registry:2) on the host, then create a kind cluster configured so node containerd (the CRI used inside kind) knows to treat that registry name as a mirror.
- The kind project publishes a complete script and explains why this mapping is necessary: container namespaces make “localhost” different inside nodes, so kind patches containerd so that the same hostname works on host and nodes. The doc shows the registry container, a cluster config with containerdConfigPatches, and a small ConfigMap to document the registry for other tools. (kind.sigs.k8s.io)
Minimal example (inspired by the kind docs)
# create a local registry container
docker run -d --restart=always -p "127.0.0.1:5001:5000" --name kind-registry registry:2
# create a kind cluster that uses containerd's certs dir (modern kind may not require the patch)
cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d"
EOF
After the cluster is up, tag your image and push:
docker tag my-app:dev localhost:5001/my-app:dev
docker push localhost:5001/my-app:dev
kubectl create deployment my-app --image=localhost:5001/my-app:dev
kind’s documentation also includes the detail that code running inside pods that needs to reach the registry should use the registry container’s name directly (e.g., kind-registry:5000) because the containerd mapping is host/node scoped. That nuance explains a lot of “it works on my host but not in the pod” mysteries. (kind.sigs.k8s.io)
Minikube: three practical patterns Minikube is flexible, and there are three common patterns for getting local images into the cluster:
1) minikube image load
- Newer Minikube exposes a direct image load API: build or save an image on the host and run
minikube image load <image>to move it into the Minikube VM/container. This is quick and intended for dev loops. Useminikube image lsto verify. (minikube.sigs.k8s.io)
2) Use the built-in registry addon (or registry-creds)
- Minikube provides a
registryaddon (andregistry-credsfor credential mapping). Enabling the registry addon deploys a registry pod inside the cluster; the docs explain how to push into it from your host (on some platforms a small port-forward or socat bridge is used) and then reference images aslocalhost:5000/name. That approach is handy when you want the registry to live alongside the cluster. (minikube.sigs.k8s.io)
3) Build directly into Minikube’s Docker environment (docker-env)
eval $(minikube docker-env)redirects your Docker CLI to the Minikube VM’s daemon. Build images there and they are immediately visible to the cluster without pushing. This is especially convenient on Linux and avoids pushing altogether. The Minikube registry handbook documents these approaches and platform caveats. (minikube.sigs.k8s.io)
Important image-pull gotchas (applies to both kind and Minikube)
- imagePullPolicy matters. Kubernetes defaults imagePullPolicy based on tag: if you use
:latest(or omit a tag) the policy defaults toAlways, which causes kubelet to try pulling from a registry even if a local image is present. Use a fixed tag and/or setimagePullPolicy: IfNotPresent(orNeverin very controlled cases) to ensure your local image is used. The Kubernetes docs explain the defaults and the rationale. (v1-33.docs.kubernetes.io)
Example deployment snippet
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
spec:
replicas: 1
template:
spec:
containers:
- name: hello
image: localhost:5001/hello:dev
imagePullPolicy: IfNotPresent
Tradeoffs and when to pick which
- kind + local registry: great for CI parity and multi-node testing. The registry-as-container approach is stable and portable; scripts can be reused in CI runners and on developer machines. The additional configuration (containerd mapping) is a one-time setup per cluster and avoids repeatedly pushing images to external registries. (kind.sigs.k8s.io)
- Minikube image load / docker-env: fastest for single-node dev loops where you don’t want a registry at all.
minikube image loadis explicit and reliable;docker-envis great when you wantdocker buildto populate the cluster directly. (minikube.sigs.k8s.io) - Minikube registry addon: useful when you want the registry to be inside the cluster or when working across multiple developer machines with a shared port mapping.
Closing note — a practical mindset Think of the registry like a practice amp on stage: small and local gets you loud feedback quickly. Use tags (not :latest), set pull policies deliberately, and pick the integration pattern that matches whether you need repeatable CI behavior (kind + registry) or the fastest local iteration (minikube image load / docker-env). The official kind and Minikube docs have ready-made examples and scripts that work across platforms — they’re a helpful reference while you wire your loop. (kind.sigs.k8s.io)