Docker Run, Attach, and Exec: How They Work Under the Hood (and Why It Matters)
You'll see the docker run command in every Docker tutorial or demo.
It looks simple enough - docker run -it debian or even docker run nginx.
But that simplicity is deceptive.
What actually happens under the hood may surprise you.
The difference between Docker's attach and exec commands is a common source of confusion -
and understandably so.
Both commands have similar arguments and, at first glance, similar behavior.
However, they aren't interchangeable.
They're designed for different use cases, and their implementations differ accordingly.
Still, it can be tricky to remember when to use which.
So let's break down what run, attach, and exec really do by looking at how they're implemented.
This understanding will make it easier to remember the right tool for the job.
And, like any real understanding, it frees you from rote memorization and gives you the power to extrapolate the knowledge to other container runtimes -
like Podman, nerdctl, containerd, and even Kubernetes 😎
Docker architecture overview
First, a quick recap of the Docker architecture:

Three takeaways that are particularly important for our discussion:
- Container management architecture is layered.
- Containers are
regular processesisolated and restricted execution environments for processes - There is a shim component between the container manager and the container.
What does docker run command really do?
When you docker run a container, it may feel like you're starting a regular foreground process -
the command blocks your current terminal session,
and the containerized application's stdout and stderr are printed to your terminal.
Anything you type in the terminal is sent to the containerized application's stdin,
and if you hit Ctrl+C, accidentally or on purpose,
the interrupt signal (SIGINT) is sent to the containerized application.
However, in reality, the docker CLI is just a (relatively thin) client of the dockerd daemon,
so the docker run process is not even a parent process of the containerized application
(and neither is dockerd). See it for yourself:
docker run -it debian
...and list all processes on the machine from another terminal:
ps auxf
To simulate the "foreground process" experience,
the docker run command establishes an elaborate client-server relay between your terminal and the containerized application,
making it look like the containerized application is the foreground process.
Remember the layered container management architecture from the previous section?
Here is what actually happens when you start a container with docker run:
terminal <-> docker <-> dockerd <-> containerd <-> shim <-> application (container)

Containerized application and stdio streams
Inside a container, there is a regular Linux process (or a few of them).
However, if the foreground process in your terminal is docker run,
the containerized application must always be a background, daemon-like process.
As every normal process, the containerized application has stdio streams: stdin, stdout, and stderr. Back in the day, when you started a process as a daemon (i.e., detaching it from the starter process), it would be reparented to PID 1, and its stdio streams would be simply closed. However, it's clearly not happening with Docker containers, allowing us to see the containerized application's stdout and stderr in our terminal, and even send data to its stdin.
🧙♂️ You shall not pass!
This tutorial is only available with the Official Premium Content Pack. Please consider upgrading your account to unlock all learning materials, get unlimited daily usage, and access to more powerful playgrounds. Help us keep this platform alive and growing!
Level up your Server Side game — Join 20,000 engineers who receive insightful learning materials straight to their inbox