exec, attach, and logs
Once the Driftwood web container is running detached, you need three different ways back to it, and people constantly reach for the wrong one. docker logs reads what the main process has written to stdout and stderr. docker exec starts a new process inside the running container — a debug shell. docker attach reconnects your terminal to the existing PID 1. Confusing attach with exec is how people accidentally kill a production container with a stray Ctrl-C.
The three are not interchangeable, and the difference comes straight from the PID 1 model in the previous topic. logs and exec never touch PID 1; attach wires your keyboard directly to it. Knowing which one you are running is the difference between inspecting a live service and stopping it.
docker logs — Reading the Main Process
The daemon captures the container's stdout and stderr through its logging driver, and docker logs driftwood-web replays them. -f follows the stream in real time the way tail -f does; --tail 100 limits the output to the last hundred lines; --since 10m bounds it by time. This is the first thing you run when a detached container misbehaves — before you shell in, before you attach, you read what it has already said.
$ docker logs --tail 50 driftwood-web # last 50 lines, then return $ docker logs -f --since 5m driftwood-web # follow, last 5 minutes onward [2024-01-15 10:42:07] gunicorn: GET /bookmarks 200 [2024-01-15 10:42:09] gunicorn: POST /bookmarks 500 ← here's the problem
docker exec — A New Process Inside
docker exec -it driftwood-web bash spawns a second process inside the container's namespaces, giving you a shell running next to the app without touching PID 1. You poke around the filesystem, check environment variables, run a one-off query — and when you exit that shell, the app keeps running, because the shell was never the main process. This is what you want 95% of the time you need to be "inside" a live container.
$ docker exec -it driftwood-web sh /app # env | grep DATABASE_URL DATABASE_URL=postgresql://db:5432/driftwood /app # exit # the app keeps running — you only killed the shell
docker attach — Reconnecting to PID 1
docker attach wires your terminal to the existing main process's stdin, stdout, and stderr. Because you are now connected to PID 1 itself, a Ctrl-C sends SIGINT to PID 1 and can stop the container. attach is for the rare case where you genuinely need the foreground process's own I/O — a REPL that runs as the main process, for instance. To leave without killing it, use the detach-key sequence Ctrl-P Ctrl-Q rather than Ctrl-C, which signals the process instead of detaching from it.
Why logs Depend on stdout/stderr
docker logs only shows what the process wrote to stdout and stderr — that is all the logging driver captures. An app that writes its logs to a file inside the container shows nothing under docker logs, and people burn time wondering why the command "doesn't work." It works fine; the logs are in a file the driver never sees. This is exactly why the twelve-factor convention of logging to stdout exists, and why the Driftwood web app should write to stdout rather than to a file.
Choosing the Right One
The rule is short: logs to read history, exec to run something new alongside the app, attach only to interact with PID 1 itself. There is one wrinkle — for a distroless or scratch image there is no shell to exec into, and alpine has sh but not bash. When the image has no shell, logs and ephemeral debug containers (Chapter 11) carry the debugging load instead.
execattachlogs- docker exec — starts a new process (a shell, usually) inside the running container; quitting it leaves the app untouched because it was never PID 1. This is what you want 95% of the time you need to inspect a live container.
- docker attach — connects to the existing PID 1's terminal; Ctrl-C there sends SIGINT to the app and can stop the container. Use it only when you need PID 1's own stdin/stdout, and leave with the detach keys Ctrl-P Ctrl-Q, never Ctrl-C.
- Using
docker attachto "check on" the Driftwoodwebcontainer and pressing Ctrl-C to leave — that SIGINT goes to PID 1 and stops the container;exec, or the detach-key escape, is what you wanted. - Expecting
docker logsto show anything when the app writes its logs to a file inside the container — the logging driver only captures stdout and stderr, so file logs are invisible there. - Running
docker logswithout--tailon a container that's been up for days and drowning the terminal in gigabytes of history — bound it with--tailor--since. - Trying to
docker exec -it … bashinto a distroless or alpine image and getting "executable not found" — distroless has no shell at all and alpine shipssh, notbash; the image dictates what you can exec. - Letting
docker logsoutput grow unbounded because the defaultjson-filedriver has no rotation — the JSON log file fills the host disk over weeks (logging drivers and rotation are Chapter 11).
- Make the application log to stdout and stderr so
docker logsand the logging driver see everything, instead of writing log files inside the container. - Use
docker exec -it … sh(orbash) for live debugging so you never risk signaling PID 1, and exit freely without affecting the app. - Reach for
docker attachonly when you truly need PID 1's own I/O, and leave with the detach keys Ctrl-P Ctrl-Q rather than Ctrl-C. - Bound log reads with
--tailand--since, and configure log rotation on the daemon (Chapter 11) so the capture file can't fill the disk.
logs/exec/attach verbs
nerdctl over containerd mirrors them
kubectl logs · kubectl exec the multi-host analogs that work the same way per container (Ch12)
ctr containerd's low-level CLI, exposing the raw task I/O these wrap
Knowledge Check
How does docker exec -it driftwood-web sh differ from docker attach driftwood-web?
execstarts a new process you can quit safely;attachconnects to PID 1, where Ctrl-C can stop itattachsafely starts a brand-new shell whileexecdangerously connects you to the existing main processexecworks only on stopped containers, whileattachworks only on containers that are still runningexecclones the container into a separate copy whileattachshares the original running container
Why does docker logs show nothing for an app that writes its logs to a file inside the container?
- The logging driver only captures stdout and stderr — a file inside the container never passes through it
docker logsreads log files but only from the/var/logdirectory, and the app wrote them elsewhere- The file is encrypted at rest by the container runtime and
docker logshas no way to decrypt and read it - The file logs are buffered internally and only get flushed out to
docker logsonce the container exits
You docker attach to a running container to look around, then press Ctrl-C to get out. What happens?
- Ctrl-C sends SIGINT to PID 1 and can stop the container — you wanted Ctrl-P Ctrl-Q or
exec - Ctrl-C cleanly detaches your terminal from the session and leaves the container itself running untouched
- Ctrl-C opens a fresh sub-shell inside the running container so you can keep poking around in it
- Nothing happens — Docker deliberately ignores Ctrl-C while you are attached in order to protect the container
Why can't you docker exec -it … bash into a distroless image?
- A distroless image ships no shell at all, so there's no
bashorshto run — debug via logs instead docker execis deliberately disabled on distroless images for security and supply-chain hardening reasons- Distroless renames the
bashbinary todash, so you simply have to call the shell by its new name - A distroless image has no running PID 1 process, so there's no namespace for Docker to exec a new process into
You got correct