cloud-devops

kubectl debug Ephemeral Containers: No-Shell Pods 2026

June 25, 2026

kubectl debug Ephemeral Containers: No-Shell Pods 2026

To debug a Kubernetes pod with no shell, run kubectl debug to inject an ephemeral container that carries its own tools. Add --target=<container> to share the application's process namespace, then read the app's filesystem through /proc/<pid>/root. For crash-looping pods, debug a --copy-to copy instead.

TL;DR

Distroless and scratch images ship only your binary — no shell, no curl, no ps — so kubectl exec has nothing to attach to. The fix is kubectl debug, which attaches an ephemeral container (stable since Kubernetes 1.251) to a running pod, sharing its namespaces without a restart. Use --target to see the app's processes, reach its files at /proc/<pid>/root, swap in nicolaka/netshoot for network tools, and use --copy-to for pods stuck in CrashLoopBackOff. Note: ephemeral containers can never be removed — deleting the pod is the only cleanup. Every command here was checked against the Kubernetes 1.36 kubectl reference (current stable, released April 20262).

What you'll learn

  • Why kubectl exec fails on a distroless or crashed container, and when to reach for kubectl debug instead
  • How to debug a pod with no shell using an ephemeral container
  • How --target shares the application's process namespace
  • How to read a distroless container's filesystem through /proc
  • How to debug a CrashLoopBackOff pod with --copy-to and --set-image
  • What the --profile flag does and why the default changed in Kubernetes 1.36
  • How to debug network issues with the nicolaka/netshoot image
  • How to debug a Kubernetes node with kubectl debug node/
  • Why you can't remove an ephemeral container, and how to clean up

Why can't I use kubectl exec on a distroless pod?

kubectl exec runs a command inside an existing container, so it needs two things that distroless and crashed containers don't have: a binary to run (usually a shell) and a live process to attach to.3 A distroless image deliberately omits the shell, ls, and every debugging tool to shrink the attack surface, so kubectl exec -it mypod -- sh returns exec: "sh": executable file not found in $PATH. A crashed container has no PID 1 left to enter at all.

kubectl debug solves both problems by bringing its own container. Instead of logging into the app container, it creates a new container — with whatever image and tools you choose — inside the same pod or a copy of it.3 That container can run a different OS, share the app's namespaces, and inspect its state without changing the original image or restarting it.

The short version of kubectl debug vs kubectl exec: use exec when the container already has a shell and you need one quick command; use debug when the image is minimal, the tools are missing, or the container has crashed.

How do I debug a pod that has no shell?

Inject an ephemeral container with kubectl debug. An ephemeral container is a temporary container Kubernetes adds to a running pod for troubleshooting; it shares the pod's network and (optionally) process namespaces but is never restarted and never part of the normal pod spec you ship.4

# Attach a busybox ephemeral container to a running, shell-less pod
kubectl debug -it mypod --image=busybox --target=myapp
  • -it keeps stdin open and allocates a TTY so you land in an interactive shell.5
  • --image=busybox is the debug image; pick anything with the tools you need.
  • --target=myapp points at the container named myapp in the pod (see the next section).

Kubernetes names the ephemeral container automatically (e.g. debugger-abcde) and you drop straight into BusyBox's shell. From here you have ps, wget, nslookup, and friends — none of which exist in the distroless app container itself. Because the ephemeral container is added live, the application keeps serving traffic the whole time; nothing restarts.4

If you only need network tools and don't care about the app's processes, you can omit --target:

kubectl debug -it mypod --image=busybox

That still shares the pod's network namespace (same pod IP, same localhost), which is enough to test connectivity from inside the pod.

How does --target share the application's process namespace?

--target=<container> tells the ephemeral container to target the processes in that container, sharing its process namespace.5 Without it, your debug container gets its own isolated PID namespace and ps shows only the debug process — useless for inspecting the app.

With --target=myapp, running ps aux inside the ephemeral container lists the application's processes too:

# Inside the ephemeral container
ps aux
# PID   USER     COMMAND
# 1     nonroot  /app/server      <- the distroless app
# 14    root     ps aux           <- your debug process

Now you can read /proc/1/environ, send signals, inspect open file descriptors under /proc/1/fd, and — most usefully — reach the app's files, which is the next section. --target relies on the container runtime supporting process-namespace targeting, which the runtimes used in current Kubernetes clusters provide.4

How do I access a distroless container's filesystem?

When the process namespace is shared, you read the target container's filesystem through the Linux /proc pseudo-filesystem: /proc/<pid>/root is a symlink to that process's root filesystem.6 First find the app's PID with ps, then browse its root:

# Inside the ephemeral container, after `ps aux` showed the app at PID 1
ls -la /proc/1/root/app/
cat /proc/1/root/etc/config/app.yaml

The app's main process is usually — but not always — PID 1, so prefer reading the PID from ps over hard-coding /proc/1/root. This is how you inspect config files, logs written to disk, certificates, or a binary's version inside an image that has no cat and no ls of its own. You're using BusyBox's tools to look at the app's files. Nothing is copied into or out of the distroless image.

How do I debug a CrashLoopBackOff pod with no shell?

An ephemeral container can't reliably debug a crash-looping container: the app's process keeps dying before you can inspect its live state, so there's nothing stable to share a namespace with.3 Instead, debug a copy of the pod with --copy-to, overriding the entrypoint so the copy starts a shell instead of the crashing command:

# Make a copy named mypod-debug and run a shell in the target container
kubectl debug mypod -it --copy-to=mypod-debug --container=myapp -- sh

--copy-to=mypod-debug creates a new pod (the original is left untouched), and everything after -- replaces that container's command, so it boots into sh instead of crash-looping. You can now run the binary by hand and watch it fail with full output.

If the image is distroless, there's no sh to override the command with — so swap the image at the same time using --set-image:

# Replace the distroless image with busybox in the copy, then get a shell
kubectl debug mypod -it --copy-to=mypod-debug \
  --set-image=myapp=busybox -- sh

--set-image=myapp=busybox changes only the myapp container's image in the copy (the syntax mirrors kubectl set image).5 Mount the same config and env as the original and you can reproduce the crash interactively. When you're done, delete the copy: kubectl delete pod mypod-debug. The original pod and its CrashLoopBackOff are never modified.

What does the --profile flag do?

--profile applies a preset bundle of security context and namespace settings to the debug container so you don't hand-craft them.7 The options are general, baseline, restricted, netadmin, and sysadmin.5

  • general — a reasonable default set of capabilities for everyday debugging.
  • baseline — compatible with the Pod Security baseline standard.
  • restricted — compatible with the Pod Security restricted standard (least privilege).
  • netadmin — adds NET_ADMIN and friends for network debugging.
  • sysadmin — a privileged profile for low-level system debugging.

Important version note: the default profile changed to general in Kubernetes 1.36. In 1.35 and earlier the default was the deprecated legacy profile.8 If a privileged operation (like reading another container's /proc files or running privileged tools) is denied on a recent cluster, set the profile explicitly:

kubectl debug -it mypod --image=busybox --target=myapp --profile=sysadmin

On clusters older than 1.36, kubectl prints a deprecation warning when you don't pass --profile, nudging you toward an explicit choice before the default flips.8

How do I debug network issues with netshoot?

Point --image at nicolaka/netshoot, a purpose-built network-troubleshooting image that bundles curl, dig, tcpdump, nmap, iperf, netstat, and dozens of other tools in one container.9

# Network-debug a pod from inside its own network namespace
kubectl debug -it mypod --image=nicolaka/netshoot --target=myapp

Because the ephemeral container shares the pod's network namespace, every command runs with the pod's own IP, DNS config, and routing table — so DNS resolution, service connectivity, and TLS handshakes behave exactly as they do for the real app:

# Inside netshoot
dig +short my-service.default.svc.cluster.local
curl -v http://my-service:8080/healthz
tcpdump -i any port 8080

For node-level or NET_ADMIN-requiring captures, combine it with --profile=netadmin.

How do I debug a Kubernetes node?

Use kubectl debug node/<node-name>. This launches a debug pod on the node, running in the host's namespaces, with the node's root filesystem mounted at /host:5

kubectl debug node/ip-10-0-1-23 -it --image=busybox --profile=sysadmin
# Inside: the node's filesystem is at /host
chroot /host
journalctl -u kubelet --no-pager | tail -50

That gives you kubelet logs, the container runtime socket, /etc/kubernetes, and host networking — useful when a node is NotReady or pods won't schedule. One caveat: the node debug pod runs in the host's IPC, network, and PID namespaces but is not privileged by default, so chroot /host and some /proc reads can fail unless you add --profile=sysadmin (which sets privileged: true).10 It's the same kubectl debug workflow, aimed at a node instead of a pod. (For resizing workloads without bouncing them, see our guide to resizing Kubernetes pods without a restart.)

Can you remove an ephemeral container from a pod?

No. Once added, an ephemeral container persists in the pod spec until the pod itself is deleted — the Kubernetes API has no operation to change or remove it.4 This is by design: ephemeral containers are recorded as part of the pod's history.

So the only cleanup is to replace the pod:

# List the ephemeral containers stuck on a pod
kubectl get pod mypod -o jsonpath='{.spec.ephemeralContainers[*].name}'

# The only way to remove them: delete the pod (a Deployment reschedules it)
kubectl delete pod mypod

If the pod is managed by a Deployment, StatefulSet, or DaemonSet, the controller recreates a fresh pod without the ephemeral container. For a bare pod, you lose it — so debug copies (--copy-to) are safer when you want disposable, deletable debugging.

Bottom line

Distroless images make production safer but break the old kubectl exec reflex. The modern answer is kubectl debug: inject an ephemeral container into a running pod with --target, read the app's files through /proc/<pid>/root, reach for nicolaka/netshoot for network issues, and fall back to a --copy-to copy when the pod is crash-looping. Remember the two gotchas — set --profile explicitly on mixed-version clusters, and treat ephemeral containers as permanent.

For more Kubernetes operations, see our walkthroughs on migrating Ingress to the Kubernetes Gateway API and adding observability with Workers Logs and Sentry.

Footnotes

  1. "Kubernetes v1.25: Combiner" — ephemeral containers graduated to Stable in v1.25 (released 2022-08-23). Retrieved 2026-06-25. https://kubernetes.io/blog/2022/08/23/kubernetes-v1-25-release/

  2. "Kubernetes Releases" — v1.36 ("Haru") is the current stable line, released 2026-04-22; supported branches 1.36/1.35/1.34. Retrieved 2026-06-25. https://kubernetes.io/releases/

  3. "Debug Running Pods" — Kubernetes documentation (ephemeral containers vs kubectl exec, copies of pods). Retrieved 2026-06-25. https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/ 2 3

  4. "Ephemeral Containers" — Kubernetes documentation (purpose, namespace sharing, and the rule that they cannot be changed or removed after creation). Retrieved 2026-06-25. https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/ 2 3 4

  5. "kubectl debug" — generated CLI reference (--target, --copy-to, --set-image, --profile Default: "general", node debug mounts host fs at /host). Retrieved 2026-06-25. https://kubernetes.io/docs/reference/kubectl/generated/kubectl_debug/ 2 3 4 5

  6. "/proc" symlinks — a process's root filesystem is reachable at /proc/<pid>/root when the process namespace is shared; standard Linux proc(5) behavior used by distroless debugging guides. Retrieved 2026-06-25. https://man7.org/linux/man-pages/man5/proc.5.html

  7. "Debug Running Pods — Debugging profiles" — Kubernetes documentation describing the --profile presets. Retrieved 2026-06-25. https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/#debugging-profiles

  8. "kubectl debug: change default profile and remove legacy profile" — kubernetes/kubectl issue #1780 (legacy default through 1.35; general becomes the default in 1.36). Retrieved 2026-06-25. https://github.com/kubernetes/kubectl/issues/1780 2

  9. "nicolaka/netshoot" — a Docker + Kubernetes network troubleshooting swiss-army container (curl, dig, tcpdump, nmap, iperf, and more). Retrieved 2026-06-25. https://github.com/nicolaka/netshoot

  10. "Debugging Kubernetes Nodes With Kubectl" — node debug pods run in host namespaces with the node filesystem at /host but are not privileged by default; use --profile=sysadmin for privileged access. Retrieved 2026-06-25. https://kubernetes.io/docs/tasks/debug/debug-cluster/kubectl-node-debug/

Frequently Asked Questions

Run kubectl debug -it mypod --image=busybox --target=myapp . It injects an ephemeral BusyBox container into the running pod, sharing the app's namespaces, so you get a shell and tools even though the app image has none.