Skip to content

Latest commit

ย 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Gocker ๐Ÿณ

A minimal, fully-functional Linux container runtime built from scratch in Go โ€” demonstrating deep knowledge of namespaces, cgroups v2, OverlayFS, networking, OCI images, and observability.

MVP 1 โ†’ MVP 2 โ†’ MVP 3 โ†’ MVP 4 โ†’ MVP 5 โ†’ MVP 6
NS    cgroups  Overlay  Network  CLI+Pull  TUI

Why This Exists

Most engineers use Docker daily without understanding what it actually does. This project builds a real container runtime from first principles. Every feature maps directly to a core Linux concept


Quick Start

Prerequisites

  • Linux kernel โ‰ฅ 5.10 (cgroup v2 required)
  • Go 1.21+
  • Root / sudo access (namespaces and cgroups require it)
  • iptables and nsenter installed
# 1. Clone and build
git clone <repo-url> && cd gocker
make build

# 2. Set up the Alpine root filesystem
sudo make rootfs

# 3. Enable IP forwarding for container NAT
sudo make setup-nat

# 4. Run your first container
sudo ./gocker run /bin/sh

# 5. Run an image from Docker Hub
sudo ./gocker pull redis
sudo ./gocker run -it redis /bin/sh

Architecture

cmd/
  gocker/
    main.go               โ† CLI entrypoint (run, ps, stop, rm, pull, exec, logs, top)
internal/
  container/
    namespace.go          โ† clone() flags, pivot_root, hostname, /proc + /dev + /tmp mounts
    cgroup.go             โ† cgroup v2 setup / teardown (memory, cpu, pids)
    fs.go                 โ† OverlayFS mount / unmount
    network.go            โ† veth pairs, bridge, iptables NAT + port mapping
    image.go              โ† image ID helpers
  image/
    pull.go               โ† Docker Registry v2 API client
    layers.go             โ† layer tar extraction with whiteout support
  state/
    store.go              โ† JSON container state (/var/lib/gocker/state.json)
  metrics/
    poller.go             โ† 500ms cgroup + /proc metric poller
    buffer.go             โ† 60-sample circular buffer per metric
  tui/
    app.go                โ† bubbletea root model โ€” tabbed dashboard (Containers / Logs / Info)
    styles.go             โ† lipgloss dark-navy palette, tab/chip/badge/panel styles
    sparkline.go          โ† Unicode block chart renderer (bars + sparklines)
tests/
  mvp1_namespace_test.go  โ† PID/hostname/FS/process isolation
  mvp2_cgroup_test.go     โ† memory OOM, pids limit, CPU limit, cleanup
  mvp3_overlay_test.go    โ† overlay mount, write isolation, /proc
  mvp4_network_test.go    โ† bridge, container IP, outbound NAT
  mvp5_e2e_test.go        โ† pull, run, ps, logs
  mvp6_tui_test.go        โ† circular buffer, history, poller (unit tests)

MVPs

MVP 1 โ€” Process Isolation (Namespaces)

Linux concepts: clone(), CLONE_NEWUTS, CLONE_NEWPID, CLONE_NEWNS, CLONE_NEWIPC, CLONE_NEWNET, pivot_root, /proc mount

sudo ./gocker run /bin/sh
# Inside the container:
echo $$          # โ†’ 1
hostname         # โ†’ <container-id>
ls /             # โ†’ alpine rootfs, not host
ps aux           # โ†’ only container processes

Implementation notes:

  • We re-exec /proc/self/exe child ... to apply namespaces before the user command starts
  • exec.LookPath MUST NOT be called before pivot_root โ€” it searches the host PATH
  • /dev is a fresh in-memory tmpfs with hand-crafted minimal device nodes โ€” the host /dev is never bind-mounted (avoids SIGWINCH leaks)
  • /tmp is mounted as tmpfs with mode=1777 (sticky bit) so privilege-dropping subprocesses (e.g. apt's GPG helper) can use mkstemp() without EACCES
  • /run and optionally /var/run also get fresh tmpfs mounts for daemon lock files
  • The rootfs path is configurable via GOCKER_ROOTFS env var (default: /var/lib/gocker/rootfs/alpine)

MVP 2 โ€” Resource Limits (cgroups v2)

Linux concepts: cgroup v2 unified hierarchy, memory.max, cpu.max, pids.max, cgroup.procs

sudo ./gocker run --memory 64m --cpus 0.5 --pids-limit 20 /bin/sh
File Purpose
memory.max Hard memory cap (bytes)
cpu.max quota period โ€” e.g. 50000 100000 = 50% of one core
pids.max Max processes in the container
cgroup.procs Enroll a PID

Cleanup: TeardownCgroups polls until cgroup.procs is empty before rmdir (kernel returns EBUSY otherwise).

MVP 3 โ€” Filesystem Layers (OverlayFS)

Linux concepts: OverlayFS, copy-on-write, lowerdir, upperdir, workdir, merged

# Directory layout
/var/lib/gocker/
  images/<image-id>/rootfs/    โ† lower (read-only base layers)
  containers/<id>/
    upper/                     โ† container writes (copy-on-write)
    work/                      โ† overlayfs internal
    merged/                    โ† final view used as rootfs

Each container gets its own upper/ so writes in container A are invisible to B.

MVP 4 โ€” Container Networking

Linux concepts: Linux bridge, veth pairs, network namespaces, iptables MASQUERADE (NAT), DNAT (port mapping)

sudo make setup-nat            # enable ip_forward
sudo ./gocker run -p 8080:80 /bin/sh
# Inside:
ip addr      # โ†’ 10.0.0.x/24
ping 8.8.8.8 # outbound NAT works

Network setup per container:

  1. gocker0 bridge created at 10.0.0.1/24
  2. veth pair: host end โ†’ bridge, container end โ†’ container net namespace
  3. IP 10.0.0.x assigned inside container
  4. Default route โ†’ 10.0.0.1
  5. iptables -t nat -A POSTROUTING -j MASQUERADE for outbound
  6. iptables -t nat -A PREROUTING -j DNAT for port mapping
  7. iptables -I FORWARD -i gocker0 -o gocker0 -j ACCEPT for inter-container traffic

MVP 5 โ€” CLI and OCI Image Pull

Linux concepts: Docker Registry v2 API, OCI image format, setns() for exec

# Pull from Docker Hub
sudo ./gocker pull alpine
sudo ./gocker pull redis
sudo ./gocker pull nginx

# Run using pulled image
sudo ./gocker run alpine /bin/sh
sudo ./gocker run -it redis /bin/sh

# Container management
sudo ./gocker ps -a
sudo ./gocker stop <id>
sudo ./gocker rm <id>
sudo ./gocker exec <id> /bin/sh
sudo ./gocker logs <id>

State store: /var/lib/gocker/state.json tracks ID, image, command, status, PID, IP, ports, timestamps.

OCI pull flow: anonymous token โ†’ manifest v2 โ†’ layer blobs (gzip tar) โ†’ SHA256 verify โ†’ extract with whiteout support.

MVP 6 โ€” TUI Dashboard (gocker top)

Libraries: bubbletea, lipgloss

sudo ./gocker top

The dashboard uses a professional dark-navy theme built entirely with Lip Gloss:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 21:03:42 โ”€โ”
โ”‚  ๐Ÿณ gocker top                                               โ”‚  โ† header + live clock
โ”œโ”€โ”€[Containers]โ”€โ”€[Logs]โ”€โ”€[Info]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ† tab bar
โ”‚  TOTAL 3   โ— RUN 2   โ— EXIT 1   FILTER: 5m                  โ”‚  โ† summary chips
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  ID         IMAGE          STATUS   CPU         MEM    NET   โ”‚  โ† table header
โ”‚  a1b2c3d4  alpine         โ— RUN    โ–ˆโ–ˆโ–ˆโ–ˆโ–‘ 54%  32 MB  โ†‘1KB   โ”‚
โ”‚> b2c3d4e5  nginx:latest   โ— RUN    โ–ˆโ–ˆโ–‘โ–‘โ–‘ 12%  56 MB  โ†‘4KB   โ”‚  โ† selected (blue)
โ”‚  c3d4e5f6  redis          โ—‹ STOP   โ”€โ”€โ”€โ”€        โ”€      โ”€     โ”‚  โ† exited (dim)
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ•ญโ”€ INSPECT  nginx:latest  (b2c3d4e5) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚
โ”‚ โ”‚  Status   โ— RUN        CPU    12.3%   CPU history (30s)   โ”‚ โ”‚
โ”‚ โ”‚  IP       10.0.0.3     Mem   56.1MB   โ–โ–‚โ–ƒโ–„โ–…โ–†โ–‡โ–ˆโ–‘โ–‘         โ”‚ โ”‚
โ”‚ โ”‚  Uptime   4m32s        TX    4.1 KB   Mem history (30s)   โ”‚ โ”‚
โ”‚ โ”‚  PID      1234         RX    1.2 KB   โ–‡โ–‡โ–†โ–…โ–…โ–„โ–ƒโ–‚โ–โ–‘         โ”‚ โ”‚
โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  [/] search  [โ†‘โ†“/jk] navigate  [Tab] switch tab  [q] quit   โ”‚  โ† footer
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Keyboard bindings:

Key Action
โ†‘ / k Move selection up
โ†“ / j Move selection down
Tab Cycle tabs (Containers โ†’ Logs โ†’ Info)
Shift+Tab Cycle tabs backwards
/ Open search overlay
a Toggle show-all (include exited > 5m)
1 2 3 4 Sort by image / CPU / mem / status
r Force refresh container list
q / Ctrl+C Quit

Architecture (3 goroutines):

Poller goroutine โ”€โ”€metricsโ”€โ”€โ–ถ CircularBuffer โ”€โ”€renderโ”€โ”€โ–ถ bubbletea renderer
(500ms ticker)                  (60 samples)              (lipgloss layout)

Root Filesystem Strategy

We use the official Alpine Linux minirootfs tarball (~5 MB). Here's why:

Option Size Pros Cons
Alpine minirootfs โœ… ~5 MB Tiny, fully offline after setup, has sh/apk Must be extracted once
BusyBox static ~1 MB Smallest possible No package manager
Ubuntu base ~100 MB More tools Very large
Dynamic OCI pull Varies Any image Needs network + MVP 5

make rootfs downloads the Alpine 3.19 x86_64 minirootfs from the official CDN and extracts it to /var/lib/gocker/rootfs/alpine. MVP 5 adds the ability to pull and run any Docker Hub image dynamically.


Testing

# Build the binary first
make build

# Set up rootfs
sudo make rootfs

# Unit tests only (safe, no root)
make test-unit

# All integration tests (requires root + rootfs)
sudo make test

# Individual MVPs
sudo go test ./tests/ -run TestMVP1 -v
sudo go test ./tests/ -run TestMVP2 -v
go test ./tests/ -run TestMVP6 -v   # unit tests, no root needed
Test What it checks Root?
TestMVP1_PIDIsolation Container sees PID 1 โœ“
TestMVP1_HostnameIsolation Hostname = container ID โœ“
TestMVP1_FilesystemIsolation Alpine rootfs visible inside โœ“
TestMVP2_MemoryLimit OOM kills process over limit โœ“
TestMVP2_PidsLimit Fork fails when limit exceeded โœ“
TestMVP2_CPULimit CPU-limited container exits cleanly โœ“
TestMVP2_CgroupCleanup No orphan cgroup dirs after exit โœ“
TestMVP3_OverlayMount Alpine rootfs visible via overlay โœ“
TestMVP3_WriteIsolation Host rootfs not polluted โœ“
TestMVP3_ProcMount /proc mounted and usable โœ“
TestMVP4_BridgeExists gocker0 bridge present โœ“
TestMVP4_ContainerHasIP Container gets 10.0.0.x โœ“
TestMVP5_PullAndRun Pull alpine, run echo hello โœ“
TestMVP6_CircularBuffer Ring buffer overflow correct โœ—
TestMVP6_ContainerHistory Metrics pushed to all buffers โœ—

Talking Points

MVP Question it answers
1 โ€” Namespaces "How do containers differ from VMs?"
2 โ€” cgroups "How does Docker enforce memory limits?"
3 โ€” OverlayFS "How does Docker's image layering work?"
4 โ€” Networking "How does container networking work?"
5 โ€” CLI + Pull "Can you ship a usable tool end-to-end?"
6 โ€” TUI Dashboard "How do you approach observability tooling?"

Key Linux Concepts Demonstrated

  • clone() with CLONE_NEW* namespace flags
  • pivot_root + mount namespace manipulation
  • tmpfs mounts for /dev, /tmp (sticky 1777), /run โ€” all ephemeral and isolated
  • cgroups v2 unified hierarchy via /sys/fs/cgroup/
  • OverlayFS: lowerdir, upperdir, workdir, merged
  • Linux bridge devices and veth pairs
  • iptables MASQUERADE (NAT) and DNAT (port mapping)
  • setns() for joining existing namespaces (exec command)
  • Docker Registry v2 API + OCI image format
  • /proc/<pid>/stat, /proc/<pid>/io, /proc/<pid>/net/dev for live metrics
  • Circular buffer for time-series metric history
  • TUI architecture with bubbletea (Elm model/update/view pattern)
  • Lip Gloss layout composition (JoinVertical, JoinHorizontal, style chaining)
  • Unicode block characters for terminal bar and sparkline charts

About

Building_Docker _clone

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages