Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

containerrt

A minimal container runtime written from scratch in Go, built to understand the Linux primitives that containers are actually made of. It implements process isolation using namespaces, filesystem isolation via pivot_root, resource limiting with cgroups v2, and a small init process that reaps zombies, forwards signals, and propagates exit status.

This is a learning project, not a production runtime. It is deliberately small and dependency-free so that every mechanism is visible and understandable.

What it does

containerrt launches a process inside an isolated environment with:

  • UTS namespace — isolated hostname
  • PID namespace — the container's process runs as PID 1 with its own process tree
  • Mount namespace — a private mount table, so mounts inside do not affect the host
  • pivot_root — the container gets its own root filesystem with the host root fully unmounted (not merely hidden, as with chroot)
  • A populated /dev — an ephemeral tmpfs with the essential device nodes (null, zero, full, random, urandom, tty)
  • cgroups v2 — configurable limits on process count (pids.max) and memory (memory.max), applied before the workload starts so limits cannot be raced
  • A real init — reaps orphaned processes, forwards SIGINT/SIGTERM to the workload, and exits with the workload's exit code (using the 128 + signal convention when the workload is terminated by a signal)

Requirements

  • Linux with cgroups v2 (unified hierarchy at /sys/fs/cgroup)
  • Go 1.21+
  • Root privileges (namespace and cgroup setup require elevated capabilities)
  • A root filesystem to run inside (for example, one produced by docker export)

Usage

sudo go run main.go run <command> [args...]

For example:

sudo go run main.go run /bin/bash

The root filesystem path is currently set in the rootfs package and should be adjusted to point at an extracted filesystem on your machine.

How it works

The runtime uses a two-phase re-exec pattern. The parent process configures the namespace flags and launches a copy of itself (/proc/self/exe) as a child. The child enters the new namespaces, sets up the hostname, performs the pivot_root, mounts /proc and /dev, and then executes the requested command.

cgroup setup happens in the parent, which has access to the child's host PID. A pipe is used as a synchronization barrier: the child blocks before executing the workload until the parent has created the cgroup, written the limits, and moved the child into it. This guarantees the workload and all of its descendants start already inside the configured limits.

Project structure

.
├── main.go        # entry point, parent/child orchestration, cgroups, init logic
├── namespace/     # namespace-related setup (hostname)
└── rootfs/        # pivot_root, /proc and /dev setup

Status

The core runtime is functional. Namespaces, pivot_root, /dev, the init layer, and cgroup limiting for pids and memory all work and have been verified empirically (escape tests, pids.current monitoring, and OOM-kill under memory.max).

Possible future work:

  • Mount /sys, /dev/pts, and /dev/shm
  • Drop Linux capabilities before executing the workload
  • User namespace support for rootless operation
  • Network namespace with a veth pair

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages