---
title: 'kubectl debug Ephemeral Containers: No-Shell Pods 2026'
description: 'Debug a no-shell distroless pod with kubectl debug and ephemeral containers: the --target flag, /proc filesystem access, and CrashLoopBackOff pod copies.'
url: https://nerdleveltech.com/kubectl-debug-ephemeral-container-no-shell
language: en
date: '2026-06-25'
category: cloud-devops
tags:
  - kubernetes
  - kubectl
  - kubectl debug
  - ephemeral containers
  - distroless
  - debugging
  - cloud-devops
  - tutorial
markdown_url: https://nerdleveltech.com/kubectl-debug-ephemeral-container-no-shell.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/kubectl-debug-ephemeral-container-no-shell'
terms: https://nerdleveltech.com/llms.txt
---

# 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.25[^v125]) 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 2026[^releases]).

## 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.[^debugpod] 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.[^debugpod] 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.[^ephemeral]

```bash
# 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.[^kubectldebug]
- `--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.[^ephemeral]

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

```bash
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.[^kubectldebug] 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:

```bash
# 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.[^ephemeral]

## 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.[^distroless] First find the app's PID with `ps`, then browse its root:

```bash
# 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.[^debugpod] Instead, debug a **copy** of the pod with `--copy-to`, overriding the entrypoint so the copy starts a shell instead of the crashing command:

```bash
# 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`:

```bash
# 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`).[^kubectldebug] 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.[^profiles] The options are `general`, `baseline`, `restricted`, `netadmin`, and `sysadmin`.[^kubectldebug]

- **`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.[^profiledefault] 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:

```bash
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.[^profiledefault]

## 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.[^netshoot]

```bash
# 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:

```bash
# 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`:[^kubectldebug]

```bash
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`).[^nodedebug] 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](https://nerdleveltech.com/resize-kubernetes-pods-without-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.[^ephemeral] This is by design: ephemeral containers are recorded as part of the pod's history.

So the only cleanup is to replace the pod:

```bash
# 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.

## FAQ

**How do I debug a pod that has no shell?**
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.

**What is the difference between kubectl debug and kubectl exec?**
`kubectl exec` runs a command inside the existing container and needs a shell already present and a live process to attach to. `kubectl debug` adds a *new* container (its own image and tools) to the pod or a copy, so it works on distroless and crashed containers.

**How do I access a distroless container's filesystem?**
Inject an ephemeral container with `--target`, run `ps` to find the app's PID, then read its root filesystem at `/proc/<pid>/root` (often `/proc/1/root`) using the debug container's tools.

**How do I debug a CrashLoopBackOff pod with no shell?**
An ephemeral container can't reliably debug a crash-looping process, so create a copy: `kubectl debug mypod -it --copy-to=mypod-debug --set-image=myapp=busybox -- sh`. The copy boots a shell instead of the crashing command, leaving the original pod untouched.

**Can you remove an ephemeral container from a pod?**
No. It stays in the pod spec until the pod is deleted. Delete the pod (a controller reschedules a clean one) — there is no API call to detach an ephemeral container.

**Why did `kubectl debug` start warning about `--profile`?**
The default debug profile changed from `legacy` to `general` in Kubernetes 1.36. On 1.35 and earlier, `kubectl` warns when you don't pass `--profile` so the change doesn't surprise you on upgrade.

## 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](https://nerdleveltech.com/migrate-ingress-to-kubernetes-gateway-api-tutorial) and [adding observability with Workers Logs and Sentry](https://nerdleveltech.com/cloudflare-workers-observability-workers-logs-sentry-tutorial).

[^v125]: "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/>
[^releases]: "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/>
[^debugpod]: "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/>
[^ephemeral]: "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/>
[^kubectldebug]: "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/>
[^distroless]: "/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>
[^profiles]: "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>
[^profiledefault]: "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>
[^netshoot]: "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>
[^nodedebug]: "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/>
