← Back to list

Containers Are Just Disguised Processes: A Low-Level View

Have you ever thought about what actually happens on your computer when you launch a container? If you jump onto a Linux machine, spin up a…

Tarun Wadhwa · 2026-06-13 16:58 · 1 claps · 6.0 min read
#docker #containers #linux #linux-namespace
Open on Medium ↗
Wiki topics: ☁️ · DevOps & Cloud 🔓 · Open Source 🔭 · Astronomy & Space

Containers Are Just Disguised Processes: A Low-Level View

Have you ever thought about what actually happens on your computer when you launch a container? If you jump onto a Linux machine, spin up a PostgreSQL container, and run a standard ps aux command on your actual host terminal, you will see the PostgreSQL process running right there next to your web browser and system daemons.

To the host operating system, the container is just another process ID. But inside the container, if you run that same pscommand, the database thinks it is the only thing running on a brand new computer.

Linux creates this exact illusion using a kernel feature called Namespaces.

The Illusion of Isolation: Linux Namespaces

Namespaces allow the operating system to fake a dedicated environment for a specific process. There are different types of namespaces that isolate different aspects of the system:

  • PID Namespace: Isolates process IDs. The database process might be PID 45290 on your host machine, but inside its own PID namespace, it becomes PID 1.
  • NET Namespace: Gives the process its own private loopback interface, IP routing table, and firewall rules.
  • MNT Namespace: Gives the process a completely isolated view of the file system, hiding the host machine’s root directory.

You don’t even need Docker to see this in action. If you are on a Linux machine, you can use a native system command called unshare to build a mini container from scratch.

Try running this in your terminal:

sudo unshare --fork --pid --mount-proc bash

This command tells Linux to spawn a new bash shell, but unshare the PID namespace from the host. If you run ps auxinside this new terminal window, you will see something remarkable. The massive list of host processes disappears. Your bash shell is now running as PID 1, completely blind to the rest of your machine.

Note for Mac Users:

The unshare command will absolutely fail on a Mac with a command not found error. MacOS has its own incredibly powerful isolation framework called Seatbelt (technically the App Sandbox mechanism). While Linux handles isolation by lying to the process about what exists on the system via namespaces, macOS handles isolation by restricting what a process is allowed to do via mandatory access control rules.

If you want a native Mac alternative that lets you sandbox a process directly from the terminal without Docker or a VM, you can use a native macOS utility called sandbox-exec.

Instead of passing namespace flags, you pass it a Scheme-based security profile that dictates exactly what system calls are permitted.

You can test this right now on your Mac terminal:

sandbox-exec -p "(version 1) (allow default) (deny file-write* (subpath \"/Users\"))" bash

This command launches a standard native bash shell on your Mac, but it tells the Darwin kernel to intercept every system call. The profile rule states: allow everything by default, but explicitly deny any file-write operations inside the /Usersdirectory.

If you step into that sandboxed shell and try to create a file on your desktop:

touch ~/Desktop/test.txt

The terminal will instantly block you with a Permission denied error. You can read files, execute scripts, and navigate around, but the process is fundamentally trapped by the kernel from altering user data.

Or

Step into a bare-bones Linux container first by running: docker run --rm -it --privileged ubuntu bash . Once inside that Ubuntu container shell, run the unshare command above to see the magic unfold.

The Stack: From Namespaces to Docker

Now that we know Linux can natively isolate a process, where does Docker actually fit into the picture?

Docker didn’t invent containerization. Linux had all these primitives ready for years. To manage these isolated processes efficiently, the industry standard relies on a low-level runtime engine called containerd and a kernel utility called runc.

When a container starts, the chain of command looks like this:

[ Docker CLI ] ➔ [ containerd ] ➔ [ runc ] ➔ [ Linux Kernel Namespaces ]
  • runc is the tool that directly talks to the Linux kernel to configure the namespaces and launch the process.
  • containerd sits a layer above, managing the lifecycle of these tasks, supervising execution, and pulling down file structures.
  • Docker sits at the top of the pyramid. It provides the high-level developer experience: building images, managing volumes, setting up user networks, and exposing a clean API.

If you wanted to, you could write a shell script that uses unshare, attaches a virtual network network interface, and downloads a zipped directory of an OS filesystem. You would have successfully built your own alternative to Docker.

Why Images Have Layers

If a container is just an isolated process, where does its filesystem come from? If your container runs a Python app on Ubuntu, how does it see Ubuntu’s file structure without running a full virtual machine operating system?

This is where the concept of layered images comes in. Docker utilizes storage drivers called OverlayFS (Overlay Filesystem).

Instead of downloading a massive monolithic disk image, a Docker image is broken up into individual, immutable, read-only layers. Each line in your Dockerfile usually creates a new layer.

┌──────────────────────────────────────────────┐
│  Writable Scratchpad Layer (Container Run)   │  <-- Deleted when container stops
├──────────────────────────────────────────────┤
│  Layer 3: Copy Application Code (Read-Only)  │
├──────────────────────────────────────────────┤
│  Layer 2: Install Python/Pip (Read-Only)     │
├──────────────────────────────────────────────┤
│  Layer 1: Base Ubuntu OS Files (Read-Only)   │
└──────────────────────────────────────────────┘

When you download an image, these layers are stacked on top of each other. The magic happens when you execute docker run.

Docker takes that stack of read-only layers and throws a microscopic, completely empty writable layer right on top. This is often called the scratchpad layer.

When your running application tries to modify a system file, Linux uses a strategy called Copy-on-Write. It leaves the original file untouched in the read-only layer below, copies it up to your temporary writable layer, and modifies it there. This explains why starting a container takes milliseconds: Docker isn’t copying gigabytes of operating system files, it is just mounting a few folders on top of each other and creating an empty scratchpad directory.

This structural design explains why your changes vanish when a container is deleted. The thin writable layer is instantly discarded. If you run docker commit, all you are doing is taking that temporary scratchpad layer and locking it down as a new read-only layer in your image library.

How Traffic Moves: Network Namespaces and Discovery

Now that our isolated process has its own filesystem and its own private network namespace, it faces a major hurdle: it is completely cut off from the world. It has no physical network card and cannot see the host network interfaces.

To get traffic into the container, Docker builds a virtual network infrastructure inside your machine’s kernel memory.

The Virtual Switch

When Docker initializes, it creates a virtual network bridge interface on your host machine, usually named docker0. Think of this as a virtual software switch living inside your computer's RAM.

The Virtual Cable

When you spin up a container, Docker creates a Virtual Ethernet pair, commonly referred to as a veth pair. You can visualize this as a virtual network cable.

Docker plugs one end of this virtual cable into the host’s docker0 bridge switch, and drops the other end right through the wall of the container's isolated network namespace, mapping it as eth0 inside the container.

HOST MACHINE
┌──────────────────────────────────────────────┐
│  [ Physical Network Card ]                   │
│               │                              │
│        [ docker0 Bridge ]                    │
│               │                              │
│       (veth host-side end)                   │
│               │  <-- Virtual Cable           │
│       (veth container-side end)              │
│               │                              │
│    ┌──────────▼──────────┐                   │
│    │ Container Namespace │                   │
│    │     [ eth0 ]        │                   │
│    └─────────────────────┘                   │
└──────────────────────────────────────────────┘

When you use the port mapping flag (-p 8080:80), you are telling Docker to establish an IP routing rule on your host machine. Any packet arriving at the host's physical network card on port 8080 is automatically forwarded across the docker0 bridge, down the veth cable, directly to port 80 inside the container.

Service Discovery

How do containers find each other when you use a custom Docker network?

Every time you create a custom network, Docker launches a tiny, internal embedded DNS server at a fixed local IP address. When Container A tries to communicate with a database by using the string hostname database-service, Docker’s internal DNS server intercepts the request, looks at its internal registry map of active containers, and converts that name into the container's private bridge IP address instantly.

The Commands Finally Make Sense

Now that you have this mental model of namespaces, layered filesystems, and virtual networks, look at standard Docker commands again. They cease to be arbitrary syntax.

  • **docker exec -it <container> bash* You aren't logging into a remote server via SSH. You are simply launching a new, ordinary bash process on your host computer, but telling the kernel to force that new process to adopt the exact same namespaces* as your already running container.
  • **docker volumes** Since the container’s filesystem is just an OverlayFS mount point, a volume is literally just a standard directory on your host machine's hard drive that bypasses the layer stack entirely, linking directly inside the process namespace.

Containers aren’t heavy black boxes. They are just standard Linux processes wearing smart disguises. Once you see the underlying operating system primitives, managing them becomes completely second nature.


메타데이터
post_id
5ae48621aeee
slug
containers-are-just-disguised-processes-a-low-level-view-5ae48621aeee
url
https://medium.com/@tarunwadhwa13/containers-are-just-disguised-processes-a-low-level-view-5ae48621aeee
canonical_url
https://medium.com/@tarunwadhwa13/containers-are-just-disguised-processes-a-low-level-view-5ae48621aeee
author_url
https://medium.com/@tarunwadhwa13
status
ok
fetched_at
2026-09-05 08:43:52