Build a Mini Docker with Bash: Master Namespaces, Cgroups & OverlayFS
This article walks you through creating a lightweight Docker‑like container runtime using Bash, explaining Linux namespaces, cgroups, and overlayfs, showing how to inspect and manipulate them, and providing a complete 130‑line script that implements pull, build, run, exec, logs, and cleanup operations.
Purpose
The goal is to understand what Docker does under the hood—Cgroup, Namespace, and RootFS—by interactively exploring these concepts in the operating system and then reproducing a simplified Docker using only shell commands.
Technical Breakdown
Namespace
Linux namespaces isolate global system resources such as UTS, IPC, mount, PID, network, and user. The three related system calls are
clone(),
setns(), and
unshare(). Each process has a directory
/proc/[pid]/ns/containing symbolic links that represent its namespace IDs.
<code># ls -l /proc/$$/ns</code><code>lrwxrwxrwx 1 root root 0 Jan 17 21:43 ipc -> ipc:[4026531839]</code><code>lrwxrwxrwx 1 root root 0 Jan 17 21:43 mnt -> mnt:[4026531840]</code><code>lrwxrwxrwx 1 root root 0 Jan 17 21:43 net -> net:[4026531956]</code><code>lrwxrwxrwx 1 root root 0 Jan 17 21:43 pid -> pid:[4026531836]</code><code>lrwxrwxrwx 1 root root 0 Jan 17 21:43 user -> user:[4026531837]</code><code>lrwxrwxrwx 1 root root 0 Jan 17 21:43 uts -> uts:[4026531838]</code>Using
nsenterand
unshareyou can move a shell into a different namespace and observe isolated resources such as hostname, IPC message queues, or network interfaces.
Cgroup
Cgroups group processes for resource accounting and limiting. They provide resource limiting, prioritization, accounting, and control. A cgroup hierarchy consists of tasks (processes), cgroups (resource groups), subsystems (controllers), and hierarchies (trees). The script uses
cgcreate,
cgset, and
cgexecto manage CPU shares and memory limits.
<code># cat /proc/cgroups</code><code>cpuset 11 1 1</code><code>cpu 4 67 1</code><code>memory 5 69 1</code><code># cat /proc/$$/cgroup</code><code>11:cpuset:/</code><code>10:pids:/system.slice/sshd.service</code><code>...</code>RootFS and OverlayFS
RootFS is the filesystem visible inside a container. Docker mounts it read‑only and adds a writable layer using a union filesystem. OverlayFS implements this with three directories: lower (read‑only layers), upper (writable layer), and work (metadata). Files are read from the upper layer first; writes trigger copy‑on‑write to the upper layer.
<code># mkdir upper lower merged work</code><code># echo "lower" > lower/in_lower.txt</code><code># echo "upper" > upper/in_upper.txt</code><code># mount -t overlay overlay -o lowerdir=lower,upperdir=upper,workdir=work merged</code><code># cat merged/in_both.txt # shows content from upper layer</code>Implementation – Bocker Script
The 130‑line Bash script (named
bocker) implements a subset of Docker commands:
pull – download an image from Docker Hub
init – create an image from a directory
images – list stored images
run – create and start a container
exec – run a command inside a running container
logs – view container output
commit – save a container as a new image
rm – delete images or containers
Key functions include
bocker_check(detect image or container),
bocker_init(prepare overlay directories),
bocker_run(set up network namespace, veth pair, bridge, overlay mount, cgroup limits, and execute the user command), and helper functions for pull, exec, and logs.
<code>#!/usr/bin/env bash</code><code>set -o errexit -o nounset -o pipefail; shopt -s nullglob</code><code>overlay_path='/var/lib/bocker/overlay' && container_path='/var/lib/bocker/containers' && cgroups='cpu,cpuacct,memory'</code><code>function bocker_check() { ... }</code><code>function bocker_init() { ... }</code><code>function bocker_pull() { ... }</code><code>function bocker_run() {</code><code> uuid="ps_$(shuf -i 42002-42254 -n 1)"</code><code> ip link add dev veth0_$uuid type veth peer name veth1_$uuid</code><code> ip netns add netns_$uuid</code><code> mount -t overlay overlay -o lowerdir=$container_path/$uuid/lower,upperdir=$container_path/$uuid/upper,workdir=$container_path/$uuid/work $container_path/$uuid/merged</code><code> cgcreate -g $cgroups:/$uuid</code><code> cgexec -g $cgroups:$uuid ip netns exec netns_$uuid unshare -fmuip --mount-proc chroot $container_path/$uuid/merged /bin/sh -c "/bin/mount -t proc proc /proc && $cmd"</code><code>}</code>Prerequisites
To run the script you need:
overlayfs support (kernel module)
iproute2, iptables
libcgroup‑tools
util‑linux ≥ 2.25.2
coreutils ≥ 7.5
Additionally create the directories
/var/lib/bocker/overlayand
/var/lib/bocker/containers, set up a bridge
br1with IP
172.18.0.1/24, enable IP forwarding, and add NAT rules.
Conclusion
The tutorial demonstrates that by writing a concise Bash script you can reproduce many Docker features, gaining deeper insight into container internals and acquiring practical skills for troubleshooting and extending container runtimes.
Efficient Ops
This public account is maintained by Xiaotianguo and friends, regularly publishing widely-read original technical articles. We focus on operations transformation and accompany you throughout your operations career, growing together happily.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.