top of page
DevCollar Private Limited Logo

Docker Is Not a VM: Containers Explained from the Linux Kernel Up

Writer: Devesh Singh
Devesh Singh
8 hours ago
9 min read

Docker is often introduced with a convenient explanation:

"A container is like a lightweight virtual machine."

That analogy is useful for about five minutes.

After that, it starts creating the wrong mental model.

A Docker container does not normally boot its own kernel. It does not need a complete guest operating system. It is fundamentally a set of Linux processes isolated and constrained using kernel features.

A better model is:

Container
   =
Normal Linux Processes
   +
Namespaces
   +
cgroups
   +
Filesystem Isolation
   +
Container Runtime

Understanding that model makes Docker networking, resource limits, process management, security, and container performance much easier to reason about.

What a Virtual Machine Actually Does

A virtual machine virtualizes hardware.

A typical architecture looks like:

Physical Server
      ↓
Hypervisor
 ┌────┼────┐
 ↓    ↓    ↓
VM 1  VM 2  VM 3
 ↓     ↓     ↓
Guest Guest Guest
Kernel Kernel Kernel
 ↓     ↓     ↓
Apps  Apps  Apps

Each VM usually runs its own operating-system kernel.

From inside the VM, applications behave as if they have their own machine.

This provides a strong isolation boundary, but it also means each VM carries the overhead of a guest OS and kernel.

What a Container Does Instead

Containers take a different approach.

On Linux, containers share the host kernel:

              Host Linux Kernel
          ┌────────┼────────┐
          ↓        ↓        ↓
     Container A Container B Container C
          ↓        ↓        ↓
       Processes Processes Processes

The processes are still managed by the same kernel.

Docker does not need to emulate an entire computer for each container.

Instead, Linux kernel features make processes see isolated environments.

This is why containers can generally start quickly and use fewer resources than full virtual machines.

Start With a Normal Linux Process

Suppose you run:

node server.js

Linux creates a process.

The kernel tracks things such as:

PID
Memory
Open files
Network sockets
User
CPU usage
Filesystem access

Now imagine starting that process while changing what it is allowed to see and consume.

Give it:

  • Its own process-ID view.

  • Its own network interfaces.

  • Its own hostname.

  • Its own filesystem root.

  • Limited CPU.

  • Limited memory.

You are getting close to the foundation of a container.

Docker provides tooling around these Linux primitives so developers do not have to assemble them manually.

Namespaces: Making Processes See Different Worlds

Linux namespaces isolate what a process can see.

Different namespace types isolate different resources.

Important examples include:

PID namespace    → process IDs
NET namespace    → networking
MNT namespace    → mount points
UTS namespace    → hostname/domain name
IPC namespace    → inter-process communication
USER namespace   → user/group IDs

Namespaces are a major reason a process inside a container can appear to have its own system.

PID Namespaces

Run:

docker run --rm ubuntu ps aux

Inside the container, you may see only a small process tree.

The container's main process may appear as PID 1.

But on the host, that same process has another PID among all other host processes.

Conceptually:

Host PID Namespace

PID 1    systemd
PID 820  dockerd
PID 9421 node

              ↓ namespace view

Container PID Namespace

PID 1    node

The process did not become a process on another kernel.

The same host kernel is presenting a different process-ID view.

This detail also explains why PID 1 behavior matters inside containers, particularly around signals and child-process cleanup.

Network Namespaces

Containers can also receive isolated network stacks.

A network namespace can provide its own:

Interfaces
IP addresses
Routing table
Firewall rules
Ports

A simplified Docker network might look like:

Container
   │ eth0
   │
 veth pair
   │
Docker Bridge
   │
Host Network
   │
Internet

This is why two containers can both listen on port 3000 internally.

They are listening inside different network namespaces.

Publishing a port:

docker run -p 8080:3000 app

connects host-side traffic on port 8080 to the container's network endpoint on port 3000.

The container does not own a tiny virtual physical machine. It has an isolated network view managed through Linux networking primitives.

Mount Namespaces and Container Filesystems

A process normally sees the host's filesystem hierarchy.

Containers need a different view.

Mount namespaces allow processes to see different filesystem mounts.

Inside a container:

/
├── app
├── bin
├── etc
├── lib
└── tmp

That root filesystem may look like a complete Linux installation, but it does not mean the container is running its own kernel.

It contains user-space files:

Shell
Libraries
Executables
Configuration
Application code

The kernel is still the host kernel.

This distinction is important:

Container Image
    ≠
Operating System Kernel

An Ubuntu container image provides Ubuntu user-space files. It does not boot an Ubuntu kernel inside the container.

Where Docker Images Fit In

A Docker image provides the filesystem and metadata used to create a container.

Consider:

FROM node:22

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

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

The resulting image contains layers representing filesystem changes.

Conceptually:

Application Layer
      ↓
Dependencies Layer
      ↓
Node Base Layer
      ↓
Read-Only Image Layers

When a container starts, the runtime adds a writable container layer on top.

Writable Container Layer
           ↓
Application Layer
           ↓
Dependencies Layer
           ↓
Base Image

This layered model makes images reusable and efficient.

Multiple containers can use the same underlying image layers while maintaining separate writable state.

Overlay Filesystems

Docker commonly uses a union filesystem such as OverlayFS on Linux.

A simplified view:

Upper Layer
(container writes)
      +
Lower Layers
(image)
      ↓
Merged View

The application sees one filesystem even though it is composed from multiple layers.

If the container modifies a file from a read-only image layer, the filesystem can use copy-on-write behavior rather than changing the original image.

This is one reason containers created from the same image remain independent.

cgroups: Controlling What a Container Can Consume

Namespaces answer:

"What can this process see?"

Control groups, or cgroups, answer:

"How much can this process use?"

Without resource limits, a containerized process can potentially compete with other processes for host resources.

Docker can apply limits such as:

docker run --memory=512m app

or:

docker run --cpus=1.5 app

Conceptually:

Host Resources
      ↓
    cgroups
  ┌────┼────┐
  ↓    ↓    ↓
App A App B App C
512MB 2GB   1GB

Modern Linux systems commonly use cgroup v2, which provides a unified hierarchy for controlling and accounting for resources.

This is not hardware virtualization.

The Linux kernel is scheduling and accounting for ordinary processes while enforcing resource policies around them.

Docker Is More Than Namespaces and cgroups

Saying "containers are namespaces plus cgroups" is a useful simplification, but Docker does more than call a couple of kernel APIs.

A modern container stack typically includes multiple layers.

A simplified flow is:

docker CLI
    ↓
Docker Engine
    ↓
containerd
    ↓
OCI Runtime
    ↓
Linux Kernel

Docker manages developer-facing concerns such as:

  • Images.

  • Builds.

  • Networks.

  • Volumes.

  • Container lifecycle.

  • Registries.

Lower-level runtimes handle the mechanics of creating the container process according to standardized runtime specifications.

The kernel provides the isolation and resource-control primitives underneath.

How Docker containers work using Linux namespaces, cgroups, container runtime, and the shared host kernel

What Happens When You Run docker run?

Suppose you execute:

docker run nginx

At a high level:

1. Find / pull image
        ↓
2. Prepare image filesystem
        ↓
3. Create container configuration
        ↓
4. Configure namespaces
        ↓
5. Configure cgroups
        ↓
6. Configure networking
        ↓
7. Start container process

At the end of the chain, the important thing running is still a process.

You can see container processes from the host using normal Linux process tools.

That is the mental model worth remembering.

Containers Do Not Boot Like VMs

A VM usually goes through something resembling:

Virtual Hardware
      ↓
Bootloader
      ↓
Guest Kernel
      ↓
Init System
      ↓
Services
      ↓
Application

A container can be much simpler:

Container Runtime
      ↓
Application Process

For example:

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

The Node process can become the container's PID 1 directly.

There may be no full init system at all.

This contributes to fast container startup.

Why Does a Container Stop When Its Main Process Stops?

Docker associates the lifecycle of a container with its main process.

If the process started as PID 1 exits:

Container PID 1
      ↓
     Exit
      ↓
Container Stops

This surprises developers who think of a container as a small VM.

Inside a VM, one application exiting does not shut down the entire machine because the operating system and init process continue running.

Inside a typical container, the main application is the primary process the container exists to run.

Why Can Linux Containers Run Different Distributions?

You can run:

docker run ubuntu
docker run alpine
docker run debian

on the same Linux host.

How?

Because distributions are not only kernels.

A distribution also includes user-space software:

Package manager
C library
Shell
Utilities
Configuration

Each image can provide different user-space environments while using the same host Linux kernel.

Conceptually:

Ubuntu User Space ─┐
Alpine User Space ─┼→ Host Linux Kernel
Debian User Space ─┘

This also explains an important limitation: containers cannot simply use an incompatible kernel as if they were VMs.

What About Docker on macOS and Windows?

Linux containers depend on Linux kernel features.

macOS does not have the Linux kernel.

So Docker Desktop typically runs Linux containers inside a lightweight Linux virtual machine.

The architecture becomes:

macOS
  ↓
Lightweight Linux VM
  ↓
Linux Kernel
  ↓
Docker Containers

The containers are still containers.

But a VM exists underneath them to provide the Linux kernel they require.

This is one reason saying "Docker is a VM" is misleading while saying "Docker Desktop may use a VM to run Linux containers on a non-Linux host" is accurate.

Windows also has multiple container modes, including Windows containers that use Windows kernel primitives and Linux containers that require a Linux environment.

Containers vs Virtual Machines

The difference is easiest to see side by side.

Virtual Machines

Hardware
   ↓
Hypervisor
   ↓
Guest OS + Kernel
   ↓
Application

Containers

Hardware
   ↓
Host OS + Kernel
   ↓
Isolated Processes
   ↓
Application

A practical comparison:

Area Container Virtual Machine

Kernel Shares host kernel Own guest kernel

Isolation Process/kernel boundary Hardware virtualization primitives

Startup Usually fast Usually slower

Size Often smaller Usually larger

OS flexibility Must fit host-kernel model Can run separate guest OS

Resource overhead Lower Higher

Typical unit Application/process Complete machine

Containers and VMs are not mutually exclusive. Production systems frequently run containers inside virtual machines.

Container Isolation Is Not Identical to VM Isolation

Because containers share a kernel, the security boundary is different from full hardware virtualization.

Container security therefore relies on several layers, including:

Namespaces
cgroups
Linux capabilities
seccomp
AppArmor / SELinux
User namespaces
Filesystem permissions
Runtime configuration

For example, Linux capabilities allow a runtime to remove powerful privileges from a process rather than treating root as one all-or-nothing permission set.

A production container should generally receive only the privileges it actually needs.

Running code inside a container does not automatically make unsafe code safe.

Volumes Exist Because Container Filesystems Are Disposable

A container's writable filesystem is usually tied to that container.

If important data exists only there:

Container
   ↓
Writable Layer
   ↓
Container Deleted
   ↓
Data Gone

Persistent data should live outside the disposable container layer.

Docker volumes provide one option:

Container
    ↓
  Volume
    ↓
Persistent Data

For example:

docker run \
  -v postgres-data:/var/lib/postgresql/data \
  postgres

The PostgreSQL process can be replaced while the volume remains.

This follows a useful container principle:

Treat containers as replaceable compute units, not as permanent machines.

Why This Mental Model Helps in Production

Understanding containers as processes explains many common Docker behaviors.

High CPU

The container is not "using CPU" independently of Linux.

Its processes are being scheduled by the host kernel, potentially under cgroup limits.

Out-of-Memory Errors

A process can hit its container memory limit even when the host still has memory available.

Port Conflicts

Containers have network namespaces, while published host ports still need valid host-side mappings.

Files Disappear

The writable container layer is not durable storage.

Signals Matter

Stopping a container sends signals to its main process, so applications should handle termination correctly.

A Container Exits Immediately

Its main process exited.

Once you stop imagining a miniature VM, these behaviors become much less mysterious.

Containers Are an Isolation and Packaging Model

Docker combines two important ideas.

Packaging

An image gives you a reproducible application filesystem:

Application
Dependencies
Runtime
Configuration defaults

Isolation

Linux provides mechanisms that control what the process sees and what resources it can consume.

Together:

Container Image
      +
Kernel Isolation
      +
Resource Controls
      +
Runtime Configuration
      ↓
Container

That combination is why containers work well for deploying applications consistently across environments.

Common Misconceptions

"Every Container Has Its Own OS"

It has its own user-space filesystem view, but Linux containers normally share a kernel.

"A Container Is Just a Small VM"

The developer experience may feel similar, but the underlying isolation model is different.

"Containers Have No Overhead"

They avoid much of the overhead of a guest OS, but networking, filesystem layers, runtime services, logging, and isolation still have costs.

"Containers Are Automatically Secure"

Isolation helps, but permissions, images, capabilities, secrets, kernel security, and runtime configuration still matter.

"One Container Should Run an Entire Server"

Containers are usually easier to operate when centered around a primary responsibility and lifecycle, rather than recreating a traditional multi-service machine inside every container.

The Linux-Kernel View of a Container

If you remember one diagram from this article, make it this:

                    Linux Kernel
                         │
       ┌─────────────────┼─────────────────┐
       │                 │                 │
   Namespaces         cgroups         Security Controls
       │                 │                 │
       └─────────────────┼─────────────────┘
                         ↓
                  Container Process
                         ↓
               Application + Libraries
                         ↓
                 Image Filesystem

Docker provides the tooling and lifecycle around this model.

The kernel does the fundamental process isolation and resource enforcement.

Conclusion

Docker containers are not lightweight virtual machines in the technical sense.

A Linux container is better understood as one or more processes running on the host kernel with isolated views of system resources and controlled access to CPU, memory, networking, filesystems, and privileges.

Namespaces create isolation.

cgroups control resources.

Layered filesystems provide the container's filesystem view.

Security mechanisms restrict privileges.

Docker and its runtime stack package these capabilities into a developer-friendly workflow.

Once you understand that a container is fundamentally an isolated process rather than a tiny computer, Docker becomes much easier to reason about—from PID 1 and port mappings to memory limits, volumes, security, and production deployments.

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating

Join the DC Club

Join our email list and get access to specials deals exclusive to our subscribers.

Thanks for submitting!

© 2026

 by DevCollar Private Limited

bottom of page