Cloud Native 32 min read

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.

Efficient Ops
Efficient Ops
Efficient Ops
Build a Mini Docker with Bash: Master Namespaces, Cgroups & OverlayFS

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

nsenter

and

unshare

you 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

cgexec

to 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/overlay

and

/var/lib/bocker/containers

, set up a bridge

br1

with 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.

cloud nativeDockercontainerbashcgroupsoverlayfsNamespaces
Efficient Ops
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.