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.



