Why kubectl debug instead of kubectl exec¶
kubectl exec -it mypod -- sh only works if the target container's image already has a shell and basic tools baked in. Minimal, distroless, or scratch-based images — increasingly the norm for production containers — have neither, so exec fails immediately with something like OCI runtime exec failed: exec: "sh": executable file not found in $PATH. kubectl debug solves this by attaching a separate, throwaway container (an ephemeral container) to the running pod, using whatever debugging image you choose, without rebuilding or restarting the pod's existing containers.
It has three main modes: attach a debug container to an existing pod, copy a pod to an isolated debug replica, or attach a debug container directly to a node.
Attaching an ephemeral container to a running pod¶
kubectl debug -it mypod --image=busybox:1.36 --target=myapp-container
--image— the debug image to run; it shares no filesystem or process namespace with the target container by default.--target— which container's process namespace to share. With--targetset, the debug container can see the target container's processes (psinside the debug container shows the app's PIDs), which is what makes this useful for inspecting a running app rather than just poking at an empty sidecar.
Common debug images:
busybox— small, has basic shell utilities (sh,cat,ps,wget).nicolaka/netshoot— purpose-built for network troubleshooting (curl,dig,tcpdump,nmap,iproute2,nc).- The distribution's own base image (e.g.
debian,ubuntu) when you need a fuller toolset or a package manager to install something on the fly.
Requires Kubernetes 1.23+ for ephemeral containers to be enabled by default (earlier versions may need the EphemeralContainers feature gate).
Copying a pod for isolated debugging¶
When you don't want to touch the running pod at all — e.g. it's serving production traffic and you'd rather not risk sharing its process namespace — --copy-to creates a new pod cloned from the original, with your changes applied only to the copy:
# Copy the pod and add a busybox debug container alongside the original containers
kubectl debug mypod -it --copy-to=mypod-debug --image=busybox:1.36 --share-processes
Useful variants:
# Crash-looping app: replace that container's command in the copy so it starts a
# shell instead of the failing entrypoint (the original image stays the same)
kubectl debug mypod -it --copy-to=mypod-debug --container=myapp-container -- sh
# Same, but also swap the image (e.g. a debug build of the app)
kubectl debug mypod -it --copy-to=mypod-debug --set-image=myapp-container=myapp:debug --container=myapp-container -- sh
# Keep the copy on the same node as the original
kubectl debug mypod -it --copy-to=mypod-debug --same-node --image=busybox:1.36
--same-node matters when the problem is node-specific (a bad disk, a local network problem, node resource pressure) rather than something that follows the pod to any node.
Recent kubectl versions remove liveness, readiness, and startup probes from the copy by default, so the kubelet won't kill it in the middle of an investigation. Pass --keep-liveness, --keep-readiness, or --keep-startup if you want them kept.
Debug profiles¶
--profile sets the debug container's security context to suit the job:
| Profile | Use for |
|---|---|
general (default) |
General-purpose debugging |
baseline / restricted |
Namespaces that enforce those Pod Security Standards |
netadmin |
Network tools that need NET_ADMIN/NET_RAW (tcpdump, iptables) |
sysadmin |
Full privileges (strace, poking at the node) |
kubectl debug -it mypod --image=nicolaka/netshoot --target=myapp-container --profile=netadmin
The copy is a completely separate pod; deleting it afterward (kubectl delete pod mypod-debug) doesn't affect the original.
Debugging a node directly¶
kubectl debug node/worker-3 -it --image=busybox:1.36
This schedules a privileged debug pod on the target node with the node's root filesystem mounted at /host inside the debug container, and shares the node's process and network namespaces. It's the equivalent of SSHing onto the node for troubleshooting, for clusters where direct node SSH access is restricted or unavailable — chroot /host from inside the debug shell gets you a normal-feeling shell on the node itself.
Because it runs privileged, this mode is usually restricted by RBAC/PodSecurity policy in any cluster with reasonably tight security posture — expect it to be denied in production namespaces that enforce a restricted Pod Security Standard.
Cleaning up¶
Ephemeral containers attached with plain kubectl debug (no --copy-to) cannot be removed individually — they persist in the pod spec until the pod itself is deleted or recreated (this is a known Kubernetes limitation, not a kubectl quirk). --copy-to pods and node debug pods, on the other hand, are ordinary pods and should be deleted explicitly once you're done:
kubectl delete pod mypod-debug
kubectl get pods | grep node-debugger # node debug pods are named node-debugger-<node>-<suffix>
Quick reference¶
| Situation | Command shape |
|---|---|
| Target has no shell, inspect it live | kubectl debug -it <pod> --image=busybox --target=<container> |
| Don't want to touch the live pod | kubectl debug <pod> -it --copy-to=<name> --image=<image> |
| Crash-looping before you can attach | kubectl debug <pod> -it --copy-to=<name> --container=<c> -- sh (overrides the entrypoint) |
| Node-level problem (disk, network, kubelet) | kubectl debug node/<node> -it --image=busybox |
| Need network tools (curl/dig/tcpdump) | any of the above with --image=nicolaka/netshoot |
See also Monitoring and Inspecting Resources with kubectl for the kubectl exec/kubectl logs commands to try before reaching for kubectl debug.