Skip to main content

Command Palette

Search for a command to run...

Docker Deep Dive — What Containers Actually Are

Updated
9 min read
Docker Deep Dive — What Containers Actually Are

In the previous article, we discussed two important ideas:

Containerization
Orchestration

Containerization packages applications.

Orchestration runs those containers at scale.

Tools like Docker and Kubernetes power this system.

But what actually happens when you run:

docker run nginx

Does Docker start a virtual machine?

Does it boot an operating system?

Not exactly.

To understand Docker properly, we need to understand one key idea:

Containers are just Linux processes.

Everything else in Docker is built around that fact.


What Docker Really Is?

Docker is a containerization platform.

It helps developers:

Build containers
Ship containers
Run containers

Docker introduced three core building blocks:

Dockerfile
Docker Image
Docker Container

These three components form the foundation of how Docker works.

But before understanding them, we need to understand what a container actually is.


Containers Are Just Linux Processes

A container is not a virtual machine.

It is simply a Linux process running with isolation.

You can think of a container as:

Application + Runtime + Dependencies

All running as an isolated process on the host system.

Under the hood, Docker simply asks the Linux kernel to start a process.

But with two important mechanisms applied:

Linux Namespaces
Linux cgroups

Namespaces create isolation.

cgroups control resource limits.

Together they form the foundation of containers.


The Role of Linux Namespaces

Linux namespaces are responsible for isolation.

They make a process believe it is running in its own environment.

In other words, namespaces create the illusion of a separate system.

A container might think it has:

its own process tree
its own network interfaces
its own filesystem
its own hostname

But in reality, everything is still running on the same host machine.

The Linux kernel simply isolates what each process can see.


Types of Linux Namespaces

Linux provides several different namespaces.

Each namespace isolates a different part of the system.

Docker combines multiple namespaces together to create containers.


1. PID Namespace (Process Isolation)

The PID namespace isolates process IDs.

Each container gets its own process tree.

Inside a container, if you run:

ps aux

You might see something like:

PID 1    node server.js
PID 7    nginx

But on the host machine, the same processes might appear as:

PID 12834    node server.js
PID 12840    nginx

Inside the container, the application sees itself as PID 1, which normally represents the main system process.

This makes the container believe it is running on its own system.

Each container therefore has its own independent process tree.


2. Network Namespace

The network namespace isolates networking.

Each container gets its own network stack including:

IP address
Network interfaces
Routing table
Port space

Example:

Container A → 172.17.0.2
Container B → 172.17.0.3

Even if both containers run the same port, they do not conflict.

For example:

Container A → port 3000
Container B → port 3000

Because they exist in separate network namespaces, both applications can run on the same port.

Docker internally connects containers through a virtual bridge network.


3. Mount Namespace (Filesystem Isolation)

The mount namespace isolates the filesystem.

Each container sees its own filesystem.

This filesystem is built from Docker image layers.

For example a container might see:

/
├ bin
├ usr
├ app
└ node_modules

Containers cannot access host files unless explicitly mounted.

This is why Docker volumes exist.


4. UTS Namespace (Hostname Isolation)

The UTS namespace controls the system hostname and domain name.

Each container can have its own hostname.

Example:

docker run --hostname myapp nginx

Inside the container:

hostname
→ myapp

Even though the host machine may have a completely different hostname.


5. IPC Namespace (Inter-Process Communication)

The IPC namespace isolates communication mechanisms between processes.

These include:

shared memory
message queues
semaphores

Containers cannot share these resources with other containers unless explicitly configured.

This prevents unintended communication between processes in different containers.


6. User Namespace

The user namespace maps users inside a container to different users on the host system.

This improves security.

For example:

Inside a container, a process may run as:

root (UID 0)

But on the host machine, that same process might actually map to:

UID 100000

So even though the process appears to have root privileges inside the container, it does not actually have root access on the host system.

This prevents containers from gaining full system privileges.


Namespace Summary

Namespace   What it isolates

PID         Processes
Network     Networking stack
Mount       Filesystem
UTS         Hostname
IPC         Process communication
User        User IDs

Together, these namespaces create the illusion that a container is running on its own machine.


What Happens When a Container Starts

When you run a command like:

docker run nginx

Docker does not start a virtual machine.

Instead, Docker asks the Linux kernel to start a new process with isolated namespaces.

The kernel creates something like this:

Container A
 ├ PID namespace
 ├ Network namespace
 ├ Mount namespace
 ├ UTS namespace
 ├ IPC namespace
 └ User namespace

The container process runs inside these isolated views of the system.

Each container gets its own set of namespaces.

Example system:

Host Machine
 ├ Container A
 │   ├ PID namespace A
 │   ├ Network namespace A
 │   └ Mount namespace A
 │
 └ Container B
     ├ PID namespace B
     ├ Network namespace B
     └ Mount namespace B

Because of this isolation:

Container A cannot see

Processes of Container B
Network interfaces of Container B
Filesystem of Container B

All containers share the same kernel, but each sees a different isolated view of the system.


What Namespaces Do NOT Do

Namespaces isolate resources.

But they do not limit resource usage.

For example, one container might consume:

100% CPU
20 GB RAM

Even though it is isolated, it can still exhaust system resources.

This is where cgroups come in.


What Are cgroups?

cgroups (Control Groups) limit how much system resources a process can use.

They control things like:

CPU usage
Memory usage
Disk I/O
Network bandwidth

Docker uses cgroups to enforce resource limits.

Example:

docker run -m 512m myapp

This limits the container to 512 MB of RAM.

The Linux kernel enforces this using cgroups.


The Real Definition of a Container

A container is essentially:

Linux Process + Namespaces (isolation) + cgroups (resource limits)

Docker simply makes it easier to create and manage this setup.


Why Containers Start Instantly

Virtual machines must:

boot an operating system
initialize hardware
start system services

Containers do none of this.

Starting a container simply means:

create namespaces
apply cgroup limits
start the process

This is why containers start in seconds or even milliseconds.


Docker Images

Docker images are read-only templates used to create containers.

They contain:

application code
runtime
libraries
dependencies

Images use a layered filesystem.

Example layers:

Base OS layer
Node runtime layer
Dependencies layer
Application code layer

Each instruction in a Dockerfile creates a new layer.

Docker caches these layers which makes builds much faster.


A Common Misconception About Images

Many people believe containers must always be created from images.

In Docker workflows this is usually true.

But technically, containers do not require images.

Remember:

A container is just a process with namespaces and cgroups.

Images simply provide a convenient way to package the filesystem required for that process.


Dockerfile

A Dockerfile is the blueprint used to build Docker images.

Example:

FROM node:20

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

CMD ["node", "index.js"]

This tells Docker:

Start from a Node.js base image
Create a working directory
Copy package.json
Install dependencies
Copy application code
Start the server

Build process:

Dockerfile → Image → Container

Container Networking

Containers often need to communicate with each other.

Docker creates virtual networks to enable this.

Example system:

Container A → API
Container B → Database
Container C → Cache

Docker automatically assigns IP addresses and allows containers to communicate using service names.

Under the hood this uses:

network namespaces
virtual ethernet interfaces
linux bridges

Docker Volumes

Containers are ephemeral.

If a container is deleted, its data disappears.

To persist data, Docker uses volumes.

A volume is a directory on the host filesystem mounted into the container.

Example:

Host directory → /var/lib/docker/volumes/mydata
Container path → /data

Types of Docker Volumes

Bind Mounts

You specify the exact host directory.

Example:

docker run -v /home/user/data:/data myapp

Anonymous Volumes

Docker creates a volume automatically without a name.

These are harder to manage.


Named Volumes

Docker manages the volume with a specific name.

Example:

docker volume create mydata

Named volumes are easier to reuse and maintain.


Putting It All Together

When you run:

docker run nginx

Docker does roughly this:

pull image
create namespaces
apply cgroups
configure networking
mount volumes
start the process

What you see as a container…

Is really just a Linux process running with isolation and resource limits.


Now we understand how Docker actually works internally.

We covered:

Linux namespaces
cgroups
Docker images
Docker volumes
container networking

But another problem still remains.

How do we run hundreds or thousands of containers across multiple machines?

How are they scheduled, scaled, and monitored?

That is where Kubernetes comes in.