Back to blog
5 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.

linuxcontainersdockerdevopssecurity
Part ofDevOps →
Contents

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.

When you decide to operate directly on the Linux kernel primitives today.

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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
while :; do :; done

On the host, inspect the busybox process:

Terminal window
top -Hp $(pgrep -f busybox)

You should see CPU usage constrained around the configured limit.

Memory Test

Still inside the container:

Terminal window
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:

Terminal window
exit
umount /tmp/mybox/merged

Then remove the temporary directories:

Terminal window
rm -rf /tmp/mybox

If any mount refuses to unmount, inspect it with:

Terminal window
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.