The most disorienting beginner moment in k3d: you build an image with docker build, the cluster says ImagePullBackOff, and you're sure the image exists. It does — in Docker. k3d nodes run on containerd, which is isolated from your Docker daemon. Docker has the image; the cluster can't see it.
Takeaways
Almost every status is diagnosed with the same three commands: kubectl describe pod (State/Reason/Events), kubectl logs --previous, kubectl get events --sort-by=.lastTimestamp.
-
ImagePullBackOff: typo in image/tag, private registry without
imagePullSecrets, Docker Hub rate limit (toomanyrequests), or the k3d containerd isolation. Fix the last withk3d image import myapp:dev -c dev, or (better) a local registry — and reference the full namek3d-registry.localhost:5000/myapp:devin the manifest. An incomplete image name is a top cause of a repeat BackOff. -
CrashLoopBackOff:
kubectl logs --previousfor the dead instance's real error; checkLast State: TerminatedExit Code. Common: app bug at startup, missing env/config, unavailable dependency, OOM, too-strict liveness, or exit code 0 (a long-running service that "exited successfully" — usually a wrong entrypoint). -
Pending:
FailedSchedulingnames the cause — usually insufficient CPU/memory forrequestson a small local cluster. Also nodeSelector/affinity, taints, unbound PVC, hostPort clash. -
OOMKilled (exit 137 = 128+9): container-level (exceeded
limits.memory) or the sneaky node-level OOM — k3d nodes live in a Docker VM, so combined limits over the VM's memory kill Pods even when each app is within its own limit. Don't overcommit. -
Service silent, Pods Running: walk
Service -> endpoints -> Pod. Empty endpoints = selector mismatch (labels are case-sensitive) or port mismatch (targetPortvscontainerPort).Running 0/1= readiness failing (removed from endpoints, not restarted). -
Tilt not updating: synced path outside build context, file not covered by
sync(),fall_back_onfile changed (forces rebuild), orrun()beforesync(). FastAPI trap: without a process restart the synced code lands but uvicorn keeps the old code in memory — use--reloadordocker_build_with_restart. -
Disk eaten: kubelet image GC is lazy (only above 85% disk).
docker system df, then prune incrementally — but-acan wipe images the cluster needs and--volumescan delete your local PostgreSQL data.
Top comments (0)