Docker & ContainersVI · Container LifecycleWorking with running containers
exec and attach — working inside a running container
What you'll learn
- Explain what `docker exec` joins and what it does not change
- Distinguish exec from attach and know which one can stop your container
- Recognise that exec-ed processes share the container cgroup and PID namespace
- Debug a container that has no shell
Prerequisites
Verified against Docker Engine 29.x · Docker Engine 28.x · Docker Compose 2.x · containerd 2.x · runc 1.2.x · BuildKit 0.20+ · Linux kernel 5.15+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-11
docker exec is the command operators reach for the moment something looks
wrong. It is also the command that produces the most misleading conclusions,
because what you see and do inside an exec session is not quite what PID 1
sees, and nothing you change survives the next deploy.
This lesson is about what exec actually is, how it differs from attach, and which diagnoses drawn from an exec session you should distrust.
What exec is
The flags are few and worth knowing precisely: -i/--interactive keeps
stdin open, -t/--tty allocates a pseudo-terminal, -d/--detach runs the
command in the background, -e/--env and --env-file add environment,
-u/--user and -w/--workdir change identity and directory, and
--privileged gives the exec-ed process extended privileges. That is the
whole list. There is no --cap-add on exec, and no way to change the network
or mount namespace of the exec-ed process — it gets the container’s.
$ docker exec web nginx -vnginx version: nginx/1.27.4Illustrative output
Note the absence of -it. You only need a TTY when something interactive is
on the other end. docker exec -it web sh in a script that has no terminal
fails with the input device is not a TTY, which is a startlingly common
cause of red CI jobs.
What attach is
docker attach does something different and much narrower: it connects your
terminal to the stdio streams of PID 1. It starts nothing. There is only
ever one PID 1, so attach is a window onto the process that already exists.
That makes attach useful for a container that expects console input — a database REPL, an interactive installer — and dangerous everywhere else:
$ docker logs -f --tail 100 --timestamps web2026-08-11T09:14:02.118374Z 192.0.2.44 - - [11/Aug/2026:09:14:02 +0000] "GET /health HTTP/1.1" 200 2
2026-08-11T09:14:12.119901Z 192.0.2.44 - - [11/Aug/2026:09:14:12 +0000] "GET /health HTTP/1.1" 200 2Illustrative output
docker exec | docker attach | docker logs | |
|---|---|---|---|
| Starts a process | Yes | No | No |
| Talks to PID 1 | No | Yes | Reads its output |
| Can signal PID 1 | Only if you send one | Yes, by default | No |
| Works on a stopped container | No | No | Yes |
| Multiple concurrent sessions | Yes | Yes, they share the stream | Yes |
Exec is not free
Because exec-ed processes live in the container’s cgroup and PID namespace, they are accounted for like any other container process.
- A container with
--pids-limit 64that is already running 60 threads will refuse yourdocker exec ... bash, and the error — a fork failure — looks nothing like “you hit the PIDs limit”. - A memory-hungry diagnostic (
jmap, a largegrep -r) runs inside the container’s memory limit. It can trigger the OOM killer, and the kernel may well pick the application rather than your shell. You went in to diagnose an incident and caused a bigger one. docker topshows the container’s processes with host PIDs, which is what you need if you want to attach a host-side profiler.
$ docker top webUID PID PPID C STIME TTY TIME CMD
root 2192 2171 0 Aug07 ? 00:00:04 nginx: master process nginx -g daemon off;
101 2245 2192 0 Aug07 ? 00:01:13 nginx: worker processIllustrative output
Everything you change is temporary
An exec session writes to the container’s writable layer. docker rm deletes
that layer. Every replacement of the container — a deploy, a
docker compose up after an image change, an Ansible run that recreates the
container — throws it away.
$ docker diff webC /etc
C /etc/nginx
C /etc/nginx/nginx.conf
A /root/.bash_history
C /var/cache/nginxIllustrative output
docker diff is the honest audit of what somebody has been doing with exec.
A is added, C is changed, D is deleted. A C on a configuration file is
a change that exists on exactly one host, in exactly one container, and is
about to vanish.
Debugging a container with no shell
Minimal and distroless images deliberately ship no sh, no ls, no
curl. docker exec -it web sh fails:
OCI runtime exec failed: exec failed: unable to start container process:
exec: "sh": executable file not found in $PATH: unknown
That is not a broken container. It is the image doing its job. Two ways in:
A sidecar sharing the target’s namespaces. Start a throwaway container in the same network and PID namespaces, with tools installed:
docker run --rm -it \
--network container:web \
--pid container:web \
--cap-add SYS_PTRACE \
nicolaka/netshootThe sidecar shares the target’s network namespace, so ss -tlnp and curl
see exactly what the application sees. --pid container:web lets you see
the target’s processes and read /proc/1/environ. What the sidecar does
not share is the mount namespace, so you cannot read the target’s files
this way.
nsenter from the host, when you need the mount namespace too. Find the
host PID first:
$ docker inspect --format '{{.State.Pid}}' web2192Illustrative output
nsenter --target 2192 --mount --uts --ipc --net --pid -- ls -l /etc/nginxnsenter runs the host’s binaries with the container’s namespaces, so you get
a working ls even in a scratch image. It requires root on the host and it
bypasses every restriction Docker applied to the container — treat it as a
privileged operation and log it.
Knowledge check
Knowledge check · 4 questions
Q1. A process started with `docker exec` differs from PID 1 in which important way?
Q2. Pressing Ctrl-C in a `docker attach` session can stop the container.
Q3. A container has `--pids-limit 64` and is already running 62 threads. Which symptoms should you expect? Select all that apply.
Q4. You need a shell inside a distroless container that has no `sh`. Name one workable approach.
Passing score: 75%. Answers are checked in this browser.