Jul 17, 20265 min

Build a Tiny Linux Container without Docker in 2026

A hands-on 2026 walkthrough for building a tiny isolated Linux container with overlayFS, cgroups, namespaces, pivot_root, and kernel primitives instead of Docker.

Containers feel mysterious until you build one yourself.

In this walkthrough, we will assemble a tiny isolated Linux environment using primitives that already exist in the kernel: overlayFS, cgroups, namespaces, and pivot_root. No Docker, no Podman, no container runtime.

Use a disposable virtual machine for this experiment. We will run privileged commands, create device nodes, mount filesystems, and change root directories. It is very easy to break a host system by accident.

The point is not to replace Docker in production. The point is to understand what Docker and similar tools are wrapping for you.

The Kernel Pieces Behind a Container

A container is not a single magic feature. It is a combination of Linux isolation and resource-control mechanisms:

  • overlayFS gives the container a writable filesystem layer,
  • cgroups enforce CPU and memory limits,
  • namespaces isolate process IDs, hostnames, mounts, networking, IPC, and cgroup views,
  • pivot_root switches the process into a new root filesystem.

The term control group, or cgroup, first appeared in the Linux kernel in 2007. It was originally known as process containers, which is a nice reminder that container tooling came after the kernel ideas.

Preparing a Private Root Filesystem

Create a directory structure for the experiment:

mkdir -p /tmp/mybox/{lower,upper,work,merged}

Download a small root filesystem archive such as Alpine, BusyBox, or a similar minimal rootfs, then unpack it into lower:

tar -xzf mini-rootfs.tar.gz -C /tmp/mybox/lower

Now layer it with overlayFS so changes stay in upper while the original files remain untouched:

mount -t overlay overlay \
  -o lowerdir=/tmp/mybox/lower,upperdir=/tmp/mybox/upper,workdir=/tmp/mybox/work \
  /tmp/mybox/merged

/tmp/mybox/merged now acts as the container's root directory.

Because overlayFS stores only modified files, a container can share a read-only base and keep a small writable layer. This is one reason container images can be efficient even when many containers share the same base.

Setting Resource Limits with cgroups

This example assumes cgroup v2.

Create a new cgroup and allow CPU and memory controls inside it:

mkdir -p /sys/fs/cgroup/mybox.slice/one
echo "+memory +cpu" > /sys/fs/cgroup/mybox.slice/cgroup.subtree_control

Cap the container at 15 percent of one CPU core and 512 MiB of RAM. Disable swap for the experiment:

echo "15000 100000" > /sys/fs/cgroup/mybox.slice/one/cpu.max
echo "512M"         > /sys/fs/cgroup/mybox.slice/one/memory.max
echo "0"            > /sys/fs/cgroup/mybox.slice/one/memory.swap.max

Why 15000 100000?

The first number is runtime in microseconds. The second number is the total period. The process may run for 15,000 microseconds out of every 100,000 microseconds, which is exactly 15 percent.

Entering New Namespaces

Switch to root, join the cgroup, then spawn a shell inside fresh namespaces:

sudo -i
echo $$ > /sys/fs/cgroup/mybox.slice/one/cgroup.procs

unshare \
  --uts --pid --mount --mount-proc \
  --net --ipc --cgroup \
  --fork /bin/bash

Now you have several isolated views:

  • UTS namespace: choose a hostname that is visible only inside the container.
  • PID namespace: the first process inside gets PID 1.
  • Mount namespace: mount points are private to this environment.
  • Network namespace: networking can be separated from the host.
  • IPC namespace: inter-process communication is isolated.

Try changing the hostname:

hostname mycontainer2026

That hostname is visible only inside this namespace.

Switching Roots with pivot_root

Move into the merged directory, make the mount tree private, pivot into the new root, and detach the old one:

cd /tmp/mybox/merged
mount --make-rprivate /
mkdir old_root
pivot_root . old_root
umount -l /old_root
rmdir old_root

pivot_root is stronger than a casual chroot demo because the old root can be detached. That prevents several classic escape patterns where the former root remains reachable.

Mounting Essential Virtual Filesystems

Without /proc, /sys, and /dev, many tools fail.

Create a minimal set:

mknod -m 666 dev/null c 1 3
mknod -m 666 dev/zero c 1 5
mknod -m 666 dev/tty  c 5 0

mkdir -p dev/{pts,shm}
mount -t devpts devpts dev/pts
mount -t tmpfs  tmpfs  dev/shm
mount -t sysfs  sysfs  sys
mount -t tmpfs  tmpfs  run
mount -t proc   proc   proc

This is still not a production-grade container environment. It is a learning environment. Real runtimes add careful mount policies, seccomp, capabilities, AppArmor or SELinux, user namespaces, image management, networking, and cleanup logic.

Replacing the Shell with an Application

For the demo, start a minimal shell from the root filesystem:

exec /bin/busybox sh

At this point, you are inside a self-made container-like environment.

The process has an isolated PID view, a private hostname, its own mount namespace, a separate root filesystem, and cgroup limits.

Verifying the Limits

CPU Test

Inside the container, run a tight loop:

while :; do :; done

On the host, inspect the busybox process:

top -Hp $(pgrep -f busybox)

You should see CPU usage constrained around the configured limit.

Memory Test

Still inside the container:

python - <<'PY'
b = bytearray(700 * 1024 * 1024)
PY

Once the allocation crosses the 512 MiB memory limit, the kernel memory controller should kill the process and print:

Killed

With swap enabled, the kernel may first push pages to disk. Disabling swap makes the out-of-memory behavior easier to observe.

Cleaning Up

Exit the container shell and unmount the overlay:

exit
umount /tmp/mybox/merged

Then remove the temporary directories:

rm -rf /tmp/mybox

If any mount refuses to unmount, inspect it with:

findmnt | grep mybox

Then unmount nested filesystems before removing the directory.

What This Teaches You

We built a small container-like environment using nothing but Linux kernel primitives:

  • overlayFS provided the writable layer,
  • cgroups enforced resource quotas,
  • namespaces isolated the process environment,
  • pivot_root moved the process into a new root filesystem.

Docker, Podman, containerd, and Kubernetes make these mechanisms usable at scale. They add image distribution, networking, metadata, security defaults, lifecycle management, and operational ergonomics.

But underneath all of that, the core ideas are the same.

Once you build a tiny container by hand, containers stop feeling like magic. They become a set of understandable kernel features assembled in the right order.

That understanding is useful when debugging production containers, hardening workloads, or explaining why isolation is powerful but not absolute.