An open API service indexing awesome lists of open source software.

https://github.com/martishin/toy-container-runtime


https://github.com/martishin/toy-container-runtime

cgroups chroot devcontainer docker golang linux namespace

Last synced: 3 months ago
JSON representation

Awesome Lists containing this project

README

          

# toy-container-runtime πŸš—

A tiny container runtime written in Go that demonstrates **how containers actually work** - no magic, just Linux
features:

- **Namespaces** (UTS, PID, Mount) β†’ isolate what a process can *see*
- **chroot + rootfs** β†’ give the process its own filesystem view
- **cgroups (pids)** β†’ limit what a process can *use*
- Minimal **mount setup** β†’ `/proc` and a scratch `tmpfs`

## Why containers?

Containers are *just Linux processes* with:

- a **restricted view** (namespaces),
- a **different root filesystem** (chroot/pivot_root with a β€œrootfs”),
- and **resource limits** (cgroups).

This makes them **portable** (ship a filesystem), **isolated** (can’t see host stuff), and **efficient** (no full VM per
app). Real runtimes (runc/containerd/Docker) add a ton of safety and orchestration around these same primitives.

## What this project demonstrates

1) **UTS namespace** β†’ custom hostname (`container`).
2) **PID namespace** β†’ your process becomes **PID 1** inside the container.
3) **Mount namespace** β†’ private mount table (no host pollution).
4) **chroot rootfs** β†’ your command runs in a Docker-built filesystem.
5) **/proc + tmpfs** β†’ mounts that userspace tools rely on.
6) **pids cgroup** β†’ best-effort process count limit.

You’ll literally see: a different hostname, PID 1, your own `/proc`, and a private tmpfs.

## How it works

- We build a root filesystem from `Dockerfile` (Alpine + python3 + bash), then `docker export` it to `./rootfs`.
- `mini_container run ...` re-execs itself as `child` with:
- `CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS` (+ `Unshare(CLONE_NEWNS)`)
- set hostname β†’ `container`
- `chroot(rootfs)` and `chdir("/")`
- make `/` a mountpoint, set mount propagation (slave β†’ private)
- mount `/proc` and a `tmpfs` at `/mytemp`
- (best-effort) put the process in a **pids cgroup** to cap forks
- Finally, it `exec`s your command (`/usr/bin/python3 /opt/app/hello.py` or `/bin/bash`).

## Prerequisites

- Linux (or macOS via Dev Containers / Docker Desktop)
- Go 1.24.5
- Docker (to build and export the rootfs)
- In devcontainers: container needs `--privileged`, `SYS_ADMIN`, and the Docker socket mounted

## Running locally

```bash
# Build the runtime
make build

# One-shot: build image -> export rootfs -> run demo app
make run
# Expected: "Hello from container!"

# Interactive shell inside the container
make bash
```

## Verify you are in a container

Inside the container shell:

```bash
hostname # -> container
mount | head # shows /proc and /mytemp tmpfs
ps -ef # PID namespace view (you're PID 1)
cat /proc/self/status | sed -n '1,12p'
```

## Project structure

```
.
β”œβ”€β”€ Dockerfile # rootfs content (Alpine + python3 + bash)
β”œβ”€β”€ Makefile
β”œβ”€β”€ app/hello.py # demo app
β”œβ”€β”€ cmd/mini_container/main.go # Cobra CLI: build/run/run-rootfs/child
β”œβ”€β”€ internal/
β”‚ β”œβ”€β”€ build/dockerfile_driver.go # docker build/create/export -> ./rootfs
β”‚ └── container/
β”‚ β”œβ”€β”€ namespaces.go # SysProcAttr (linux)
β”‚ β”œβ”€β”€ fs.go # mount propagation, /proc, tmpfs
β”‚ β”œβ”€β”€ cgroups.go # pids cgroup (v1/v2 best-effort)
β”‚ └── runner.go # Run / RunChild
β”œβ”€β”€ scripts/build-rootfs.sh
└── go.mod / go.sum
```