> ## Content Index
> Fetch the complete content index at: https://www.takeyourpills.tech/llms.txt
> Use this file to discover other available public pages before exploring further.

# What actually happens when you type 'docker run'
- URL: https://www.takeyourpills.tech/what-actually-happens-when-you-type-docker-run/
- Published: 2026-07-17T11:00:00.000Z
- Updated: 2026-07-17T10:59:59.000Z
- Description: Running a Docker container feels like magic, but it relies on standard Linux primitives. This breakdown traces the exact journey from a simple CLI command to a fully isolated, running Linux process utilizing namespaces and cgroups....
- Author: Youre Pena
- Tags: Docker, Linux, Systems Engineering, #Import 2026-08-24 14:51

![audio-thumbnail](https://storage.ghost.io/c/1b/10/1b10f65c-6c75-4dae-a08e-e5ba5b0da947/content/images/2026/07/cover-10.png)

What actually happens when you type 'docker run'

0:00

/0

1×

Clinical Summary

Diagnosis

Treating containers as lightweight virtual machines causes mental model breakdowns during production debugging, leading to confusion when isolated processes hit kernel-level resource limits despite available host capacity.

Prescription

- **Execution Chain:** Trace the lifecycle handoff from the CLI to **dockerd**, **containerd**, and finally **runc**.
- **Kernel Isolation:** Recognize that Linux **namespaces** restrict what a process can see, abstracting PIDs, mounts, and networks.
- **Resource Ceilings:** Debug out-of-memory errors by checking **cgroups** accounting limits instead of physical host hardware.

Side Effects

Local development environments on macOS and Windows silently introduce a hidden **hypervisor**, causing file system and networking behaviors to differ from native Linux production environments.

Potency

A container is not a magic black box or a tiny computer; it is simply a standard Linux process bounded by kernel rules.

#### Script

You type `docker run nginx`. A split second later, a web server is up, listening on port eighty, ready to serve traffic. For a long time, the easiest way to think about what just happened was to picture a tiny, lightweight virtual machine. It boots up, it has its own isolated file system, it feels like an independent computer.

But that mental model breaks down the minute things go wrong in production. A container is **not a machine**. It is a standard Linux process wrapped in kernel features and handed off through a surprisingly deep chain of command. Let us trace exactly what happens when you hit enter, right down to the Linux kernel.

## The Chain of Command: From CLI to Kernel

It starts with the Docker CLI. The CLI itself does not run your container. It is just a frontend client. When you type `docker run`, the CLI parses your arguments, builds a JSON payload, and fires an HTTP request over a local Unix socket to the Docker daemon.

This daemon is called `dockerd`. `dockerd` is the high-level coordinator. Its first job is to figure out if it actually has the files needed to run your request. It checks its local disk cache for the `nginx` image. If the image is not there, `dockerd` reaches out to a registry, like Docker Hub or Amazon ECR, to pull it down.

It does not just download a single massive file. Docker images are built using a stacked file system. They are composed of multiple read-only layers. `dockerd` pulls these individual compressed tarballs and unpacks them. It then configures a storage driver on the host system to stack these layers on top of each other. The bottom layer might be a basic Debian operating system file system. The next layer adds required network libraries. The top layer adds the nginx binary. Finally, `dockerd` slaps a thin, empty, writable layer right on top of that stack.

This is why you can write files inside a running container without permanently modifying the underlying image. Any changes you make are caught by that top writable layer.

Once the file system is prepped, `dockerd` is ready to execute. But `dockerd` does not start the container directly. It passes the request down the chain to a separate daemon called `containerd`. `containerd` manages the actual lifecycle of the container. It takes the prepared image layers and extracts them into a root file system directory. Then, it translates the configuration instructions from `dockerd` into an open standard called the OCI specification. It creates a configuration file called `config.json`. This file, combined with the extracted root file system, forms what is known as an OCI bundle.

The bundle contains every exact detail needed to run the process: the executable path, the environment variables, the user ID, and the specific kernel isolation parameters.

You might ask why Docker needs three separate components just to start a single container. Why pass the baton from the CLI, to `dockerd`, and then to `containerd`? The answer is crash tolerance and modularity. In the early versions of Docker, the primary daemon did everything. If that daemon crashed, or if you needed to restart it for an upgrade, every single running container on your host died with it. By breaking the architecture apart, `dockerd` handles the high-level API and image pulling, while `containerd` focuses strictly on execution state.

But the chain does not stop at `containerd`. It passes the OCI bundle to one final utility called `runc`. `runc` is the lowest-level tool in the stack. Its only job is to interact directly with the Linux kernel to create the isolated environment, start the process, and then immediately exit.

`runc` reads the `config.json`, issues the exact system calls required to set up isolation, starts your `nginx` process, and then terminates. If `runc` exits, what is keeping track of your running container? `containerd` injects a tiny background process called a **shim** between itself and your container. The shim sits there, holding open the standard input and output streams, ready to report the exit status back to `containerd` if the container crashes. Because of this shim, you can restart the entire Docker daemon, and your `nginx` container will keep running without interruption.

## What Provides the Isolation?

We have traced the command all the way down to the running process. But if a container is just a Linux process, and there is no guest operating system or hypervisor managing it, what actually prevents this process from accessing your host machine? What stops a compromised container from looking at your host file system or killing other processes?

The isolation comes entirely from the Linux kernel, specifically through a feature called **namespaces**. Namespaces act as a reality distortion field for a process. They limit what the process can see. When `runc` starts your container, it asks the kernel to create a new set of namespaces.

### PID Namespace

The first is the PID, or Process ID, namespace. Your host operating system might see the `nginx` process running as PID `45200`. But because of the namespace, the process inside the container looks around and thinks it is PID `1`. It cannot see any other processes on the host. If it tries to send a kill signal, it literally has no way to address processes outside its namespace.

### Mount & Network Namespaces

`runc` also creates a mount namespace. This ensures the process only sees that stacked overlay file system prepared earlier. It cannot see the host root directory, your home folder, or your host configuration files. It creates a network namespace as well. This gives the process a completely isolated network stack. It gets its own loopback interface, its own IP address, and its own routing table. The process is entirely oblivious to the physical network adapters on the host machine.

## The Hypervisor Caveat: Linux vs. macOS & Windows

This brings up an important point regarding hypervisors. You will often hear the claim that containers are fundamentally different from virtual machines because there is no guest OS and no hypervisor involved. That statement requires a major caveat. It is only strictly true on native Linux. If your production environment runs on Amazon Linux or Ubuntu, your containers are just isolated processes running directly on the host kernel.

But if you are developing locally on a Mac or a Windows machine, Docker Desktop is **absolutely running a hidden Linux virtual machine** with a hypervisor under the hood. Mac and Windows kernels do not have Linux namespaces. Docker Desktop spins up a lightweight VM via Hyper-V or the macOS Virtualization framework, and runs your containers inside that guest Linux kernel. Your production environment has no hypervisor, but your local development environment absolutely does. Keeping that distinction clear saves you from a lot of confusion when testing file system performance or network bridges locally.

## Resource Limits: Control Groups (cgroups)

Namespaces dictate what a container can *see*. But they do not dictate what a container can *use*. That is handled by another kernel feature called **cgroups**, short for control groups. Cgroups place a hard physical ceiling on resources. They limit CPU usage, memory consumption, and block device IO.

Picture this: It is a Tuesday afternoon, and your primary application container is repeatedly crashing with an Out Of Memory error. You connect to the host machine, check the system resources, and you see thirty-two gigabytes of RAM sitting completely available and idle.

If you are still applying the mini-virtual machine mental model, this situation makes absolutely no sense. The host machine clearly has memory. Why can't the container just use it?

## How This Changes Troubleshooting

When you shift your mental model to understand that a container is just a Linux process bounded by a cgroup, the answer reveals itself immediately. You are not debugging a machine running out of memory. You are debugging a process that has hit a cgroup ceiling.

When `runc` started that container, it told the kernel to enforce a strict memory limit based on your configuration. As soon as your application process attempts to allocate memory beyond that cgroup limit, the Linux kernel Out Of Memory killer wakes up and instantly terminates the process. The host has thirty-two gigabytes of free RAM, but the kernel enforces the cgroup limit regardless of host capacity.

Understanding this architecture completely changes how you troubleshoot. You stop looking for virtual motherboard issues and start looking at the cgroup accounting limits applied to the process. The same logic applies to networking errors. If your container cannot reach an external database, you do not need to debug a virtual network adapter. You need to verify the routing rules inside that specific network namespace. Then, you step outside the namespace and check how the host kernel is forwarding packets via iptables and virtual ethernet pairs.

## Conclusion: The Handoff in Review

The running container is simply a regular Linux process, wrapped in specific PID, network, and mount namespaces, bounded by a cgroup limit, and monitored by a shim. Tracing `docker run` exposes the reality of modern infrastructure. You are initiating a handoff. The CLI talks to `dockerd`. `dockerd` pulls the image layers and talks to `containerd`. `containerd` builds the OCI bundle and talks to `runc`. `runc` configures the kernel namespaces and starts the process.

When you internalize this sequence, you stop treating containers like magic black boxes. You know exactly where to look when an image pull fails, when a configuration is rejected, or when an application is forcefully killed by the kernel. You are no longer managing tiny isolated computers. You are managing Linux processes.

This is [TAKEYOURPILLS.TECH](https://takeyourpills.tech/?ref=takeyourpills.tech). Go ship something.

## References

- [EP221: How Docker Works Under the Hood](https://blog.bytebytego.com/p/ep221-how-docker-works-under-the?ref=takeyourpills.tech) \- ByteByteGo