What is a Container?
Think of a container as:
A running application + everything it needs (libraries, configs, tools)
All bundled into a lightweight package that runs on any machine with Docker installed.
It’s not a full virtual machine — it shares the host’s operating system kernel, but keeps its processes isolated.
The Key Concepts Behind Docker
Docker is built on Linux kernel features that isolate processes and control resources.
- Namespaces Give each container its own isolated view of the system (own processes, network, mounts, etc.)
- Control Groups (cgroups) Limit and track resource usage (CPU, memory, IO)
- Union File System (OverlayFS) Combines read-only image layers + a writable container layer
How a Docker Container Runs?
Here’s the lifecycle:
Step 1 – Build or Pull an Image
You either create a Dockerfile and build it (docker build) or download from Docker Hub.
An image = a stack of read-only layers, each layer adding files or configs.Step 2 – Start the Container
You run docker run myimage.
Docker creates:
New namespaces → process IDs, network, filesystem are isolated.
New cgroup → limits CPU/memory.
OverlayFS mount → read-only image layers + a writable layer for changes.Step 3 – Process Execution
The container runs the command from the image (like CMD or ENTRYPOINT in the Dockerfile).
Inside the container, it feels like its own tiny OS, but it’s really just processes on the host.Step 4 – Networking
By default, containers get their own virtual Ethernet interface.
Docker bridges it to the host network (docker0 bridge) unless you configure differently.Step 5 – Data Persistence (Optional)
Containers are ephemeral by default.
Use volumes or bind mounts to store data outside the container’s writable layer.Step 6 – Stop & Remove
Stopping a container stops its process.
Removing it deletes its writable layer, but the original image stays intact.
Why It’s Different from a VM
Here's a clear breakdown of how containers and virtual machines (VMs) differ.
Create a Container-like Process on Linux
let’s go step-by-step and create a container-like process on Linux manually, without Docker, using namespaces + cgroups.
Prerequisites
Before we begin, ensure you have a Linux system with root or sudo privileges. Additionally, you should have a basic understanding of Linux namespaces, control groups (cgroups), and chroot.
I am using WSL2 (Windows Subsystem for Linux) to execute the commands.
Goal:
- An isolated process (its own PID, hostname, mounts, network) → Namespaces
- Limited CPU and memory → cgroups
- A root filesystem from a minimal Linux image (like Alpine)
1. Create a Root Filesystem
Open your WSL2 terminal and Run this command with sudo
sudo mkdir /container
cd container/
We can use Alpine Linux (lightweight) as our container root. Download Alpine Linux minirootfs (adjust version if needed)
sudo wget https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/x86_64/alpine-minirootfs-3.18.4-x86_64.tar.gz
Extract into folder
tar -xzf alpine-minirootfs-3.18.4-x86_64.tar.gz -C .
These commands sets up an Alpine Linux environment in the /container directory.
2. Create an Isolated Namespace
Namespaces are a kernel feature that make a process think it has its own dedicated resources. We will use the unshare command to create and isolate namespaces.
unshare --pid --mount --uts --ipc --net --fork chroot /container /bin/sh
What It Does:
Creates new namespaces for PID, mount, network, and UTS (hostname). In the new PID namespace. Runs a shell directly in the isolated environment.
- --pid → New process ID space
- --mount → Separate mount table
- --uts → Separate hostname
- --ipc → Separate System V / POSIX IPC space
- --net → Own network stack
- chroot → Changes root to our Alpine filesystem
You are now inside a shell with isolated namespaces.
Set a hostname for your container:
hostname container
3. Add Resource Limits with cgroups
Control groups (cgroups) allow you to limit, prioritize, and monitor system resources for processes. Here's how to set up memory and CPU limits directly using the cgroup filesystem. cgroups mounted (usually at /sys/fs/cgroup/)
The key steps are:
- Create the cgroup directory
- Set the limit by writing to the control file
- Add the process by writing its PID to cgroup.procs
Check if cgroup v2 is available in WSL. If you see nodev cgroup2
You can mount.
grep cgroup /proc/filesystems
Mount cgroup2
mkdir -p /sys/fs/cgroup
mount -t cgroup2 none /sys/fs/cgroup
Create a cgroup
mkdir /sys/fs/cgroup/container
echo "50000 100000" > /sys/fs/cgroup/container/cpu.max
echo $((100*1024*1024)) > /sys/fs/cgroup/container/memory.max
echo $$ > /sys/fs/cgroup/container/cgroup.procs
4. Run a process inside this cgroup:
sh -c "echo $$ > /sys/fs/cgroup/cpu/mygroup/cgroup.procs; exec yes > /dev/null"
The program or task will try to use the CPU as much as possible, potentially reaching 100% utilization, but within the limit you set.
Check usage
cat /sys/fs/cgroup/mygroup/cpu.stat
cat /sys/fs/cgroup/mygroup/memory.current
Lifecycle
- Docker CLI → sends create request to dockerd.
- dockerd → talks to containerd.
- containerd → uses runc (OCI runtime) to:
- unshare() namespaces.
- Mount root filesystem (using overlayfs, bind mounts, volumes).
- Set up network namespace (via bridge, veth pairs).
- Apply cgroup limits.
- execve() the container’s PID 1 process inside that isolated environment.
References:
Parts of this content were generated with the assistance of ChatGPT, Claude and Microsoft Copilot.
Top comments (0)