Guide DockerBackendDevopsLinuxSystem Design

How Docker Actually Works Under the Hood

Most developers use Docker without knowing what is actually happening when they run docker run. Here is what namespaces, cgroups, and image layers actually do.

On this page

How Docker Actually Works Under the Hood

Most developers who use Docker daily cannot answer this question: what actually happens when you run docker run? Not the output. The mechanics. What the kernel does. What gets created. What gets limited.

The honest answer is that a container is not a virtual machine, not a sandbox, and not magic. It is a regular Linux process running on the host kernel, wrapped in two kernel features you have probably never thought about: namespaces and cgroups. Understanding those two things is understanding Docker.

Containers vs Virtual Machines

The comparison comes up constantly, and it matters. A virtual machine runs its own kernel on top of a hypervisor. It is a full operating system living inside your operating system. Heavy to start, expensive in memory, but genuinely isolated down to the hardware level.

A container shares the host kernel. There is no separate OS, no hypervisor, no hardware virtualization. When you run a container, you are starting a process on the same kernel as everything else on your machine. What makes it feel like its own environment is that the kernel has been told to hide everything outside the container from that process.

That is why containers start in milliseconds. There is no OS to boot. There is no kernel to load. The kernel is already running.

Namespaces: What the Container Can See

A namespace is a Linux kernel feature that controls what a process can see. Each type of namespace hides a different part of the system.

When Docker starts a container, it creates a set of new namespaces and runs your process inside them. From inside the container, the process thinks it is alone on the machine. From outside, it is just another process on the host.

The namespaces Docker uses:

PID namespace gives the container its own process ID space. The first process in the container gets PID 1. It cannot see any processes running outside its namespace. On the host, that same process has a completely different PID.

Network namespace gives the container its own network stack. Its own virtual network interface, its own IP address, its own routing table. That is why you have to explicitly publish ports with -p to get traffic in. The container’s network is not your host’s network.

Mount namespace gives the container its own view of the filesystem. It does not see the host filesystem. It sees whatever was set up in its image, plus any volumes you explicitly mounted.

UTS namespace lets the container have its own hostname. That is why hostname inside a container returns the container ID instead of your machine name.

IPC namespace isolates inter-process communication. Processes inside the container cannot send signals to processes outside it.

Together these namespaces create the illusion of an isolated system. The process is not actually isolated from the kernel. It is just isolated from everything running outside its namespace group.

cgroups: What the Container Can Use

Namespaces control visibility. cgroups (control groups) control resource consumption.

Without cgroups, one container could eat all available CPU and starve everything else on the host. One container could allocate all available memory and crash the machine. cgroups prevent that by putting hard limits on what a process can use.

When Docker runs a container, it creates a cgroup for it and sets the limits you specified:

docker run --memory="512m" --cpus="1.5" nginx

That translates to kernel-level limits on that container’s cgroup. The process can never use more than 512MB of memory or more than 1.5 CPU cores, regardless of what else is available on the host.

You can see this directly on Linux:

cat /sys/fs/cgroup/memory/docker/<container-id>/memory.limit_in_bytes

If your container gets OOMKilled, that is cgroups enforcing the memory limit. The kernel killed the process because it tried to exceed what its cgroup allowed.

Image Layers: How the Filesystem Works

Every Docker image is a stack of read-only layers. Each line in your Dockerfile that changes the filesystem creates a new layer.

FROM ubuntu:22.04          # layer 1 - base OS files
RUN apt-get install nginx  # layer 2 - nginx binaries added
COPY ./config /etc/nginx   # layer 3 - your config files

These layers are stored separately and can be shared between images. If ten images all start from ubuntu:22.04, they all reference the same layer on disk. Only the differences between images take additional space.

When you start a container, Docker adds one more layer on top of all the read-only image layers: a writable layer called the container layer. Everything you write inside a running container goes into this layer. Delete the container and this layer disappears with it. The image layers underneath are untouched.

This is called a union filesystem, specifically OverlayFS on most modern Linux systems. It merges multiple read-only layers with one writable layer into a single coherent filesystem view.

container layer (writable)     ← your runtime changes go here
    +
image layer 3 (read-only)      ← your config
    +
image layer 2 (read-only)      ← nginx
    +
image layer 1 (read-only)      ← ubuntu base
    =
what you see inside /

This is also why pulling an image is fast when you already have some layers. Docker checks which layers you already have and only downloads the ones you are missing.

What Actually Happens When You Run docker run

Putting it together, here is what happens when you type docker run nginx:

  1. Docker daemon receives the command and checks if the nginx image is already on disk. If not, it pulls it layer by layer from the registry.
  2. Docker creates a new set of namespaces: PID, network, mount, UTS, IPC.
  3. Docker sets up OverlayFS, stacking the image layers with a fresh writable container layer on top.
  4. Docker creates a cgroup for the container and applies any resource limits you specified.
  5. Docker sets up a virtual network interface for the container and connects it to the host via a bridge.
  6. Docker calls the kernel to start the container process inside the new namespaces.

That process sees PID 1, its own hostname, its own network, and a filesystem that looks like a complete Ubuntu or Alpine install. The host sees a process with a regular PID alongside everything else running.

Where Docker Breaks

Containers share the host kernel. That sounds obvious but the implications trip people up in production.

If the host kernel has a vulnerability, every container on that host is exposed. Namespaces hide things from processes, but a kernel exploit can bypass namespaces entirely. This is the fundamental security gap between containers and VMs.

Running containers as root is the most common mistake. A process running as root inside a container is running as root on the host kernel, just inside a namespace. If that container is compromised and the attacker finds a namespace escape, they have root on your host. Always run containers with a non-root user.

Writable container layers are ephemeral. Anything written inside a container that is not in a mounted volume is gone when the container stops. Developers who treat containers like persistent servers and write important data inside them get a painful lesson when the container restarts. This storage boundary is particularly critical when running persistent stateful systems like Apache Kafka inside containers.

Volume mounts punch a hole through the mount namespace. When you mount a host directory into a container with -v, you are explicitly giving that container access to your host filesystem at that path. Mounting sensitive directories like /etc or /var/run/docker.sock gives the container significant power over the host.

The One Thing to Take Away

A container is a process with a restricted view of the system, controlled resource budget, and a layered filesystem. Namespaces define what it can see. cgroups define what it can use. OverlayFS defines what its filesystem looks like. There is no separate OS, no hardware virtualization, no magic. Just Linux kernel features that have existed since 2008, assembled into a developer-friendly tool.