A distroless image (Choosing a Base Image) has no shell and no ls, so docker exec has nothing to run. docker debug attaches a toolbox (a shell, curl 3,008 , vim, htop 386,022 and any Nix 17,784 package on demand) to a container or image without changing it. It is proprietary and ships only with Docker Desktop 514 , free for all users since Desktop 4.50; Engine on Linux has no such command, and the request to add it (docker/roadmap issue #893, February 2026) is still open. On Engine, the same idea works with namespaces:
docker debug l3-dl 2>&1 | head -1
docker run -d --name l3-dl gcr.io/distroless/nodejs24-debian13 \
-e 'require("http").createServer((q, s) => s.end("ok\n")).listen(3000)' >/dev/null
docker exec l3-dl sh 2>&1 | grep -o 'exec: .*'
docker run --rm --pid container:l3-dl --network container:l3-dl busybox:1.38 \
sh -c 'ps | head -2 | cut -c 1-50; netstat -tln | tail -1; ls /proc/1/root/nodejs/bin'
R=https://github.com/iximiuz/cdebug/releases/download/v0.0.19
curl -Ls $R/cdebug_linux_amd64.tar.gz | tar xz cdebug
./cdebug exec --rm l3-dl sh -c 'wget -qO- localhost:3000; which wget; ls / | head -6' \
| tr '\n' ' '; echo
docker rm -f l3-dl >/dev/nulldocker: unknown command: docker debug
exec: "sh": executable file not found in $PATH
PID USER TIME COMMAND
1 root 0:00 {MainThread} /nodejs/bin/node
tcp 0 0 :::3000 :::* LISTEN
node
ok /.cdebug-258fda0d/bin/wget bin boot dev etc home libThe BusyBox 65,982 container joined the target's PID and network namespaces, so its ps sees Node.js 2,131 as PID 1, its netstat sees port 3000, and /proc/1/root exposes the target's files. cdebug 1,675 (github.com/iximiuz/cdebug (https://github.com/iximiuz/cdebug 1,675 ), Apache-2.0) automates this: it starts BusyBox in the target's namespaces, then chroots into the target's root with its tools in a hidden /.cdebug-* directory on the PATH, so ls / shows the distroless filesystem, yet wget works.

Kubernetes 5,150 builds this pattern in as kubectl 5,150 debug (Kubernetes). Treat debug access in production like shell access.