Skip to main content
RunBook Academy

Docker & ContainersVI · Container LifecycleWorking with running containers

exec and attach — working inside a running container

Intermediate⏱ ~22 mindocker

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

Not yet marked complete on this device.

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.

Read-only / Safeexec
$ docker exec web nginx -v
nginx version: nginx/1.27.4

Illustrative 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:

Read-only / Safelogs
$ docker logs -f --tail 100 --timestamps web
2026-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 2

Illustrative output

docker execdocker attachdocker logs
Starts a processYesNoNo
Talks to PID 1NoYesReads its output
Can signal PID 1Only if you send oneYes, by defaultNo
Works on a stopped containerNoNoYes
Multiple concurrent sessionsYesYes, they share the streamYes

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 64 that is already running 60 threads will refuse your docker exec ... bash, and the error — a fork failure — looks nothing like “you hit the PIDs limit”.
  • A memory-hungry diagnostic (jmap, a large grep -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 top shows the container’s processes with host PIDs, which is what you need if you want to attach a host-side profiler.
Read-only / Safetop
$ docker top web
UID     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 process

Illustrative 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.

Read-only / Safediff
$ docker diff web
C /etc
C /etc/nginx
C /etc/nginx/nginx.conf
A /root/.bash_history
C /var/cache/nginx

Illustrative 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:

Configuration changesidecar
docker run --rm -it \
--network container:web \
--pid container:web \
--cap-add SYS_PTRACE \
nicolaka/netshoot

The 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:

Read-only / Safepid
$ docker inspect --format '{{.State.Pid}}' web
2192

Illustrative output

Service impact possiblensenter
nsenter --target 2192 --mount --uts --ipc --net --pid -- ls -l /etc/nginx

nsenter 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

  1. Q1. A process started with `docker exec` differs from PID 1 in which important way?

  2. Q2. Pressing Ctrl-C in a `docker attach` session can stop the container.

  3. Q3. A container has `--pids-limit 64` and is already running 62 threads. Which symptoms should you expect? Select all that apply.

  4. 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.