> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build Your Own Docker

> Master Linux internals by building a container runtime from scratch — namespaces, cgroups, and overlay filesystems

<img src="https://mintcdn.com/devweeekends/AEOaWh79Ur7CdHHv/images/courses/build-your-own-x/docker-namespaces.svg?fit=max&auto=format&n=AEOaWh79Ur7CdHHv&q=85&s=6f377a5208c3cfa6c6a70e5ae5760cea" alt="Container Isolation" width="1080" height="1080" data-path="images/courses/build-your-own-x/docker-namespaces.svg" />

# Build Your Own Docker

**Target Audience**: Senior Engineers (5+ years experience)\
**Language**: Java (with Go & JavaScript alternatives)\
**Duration**: 4-6 weeks\
**Difficulty**: ⭐⭐⭐⭐⭐

***

## Why Build Docker?

Containers are the **foundation of modern infrastructure**. Every major company runs containers. By building your own Docker:

* **Master Linux internals** — namespaces, cgroups, capabilities
* **Understand container security** — isolation mechanisms, seccomp, AppArmor
* **Learn filesystem concepts** — overlay filesystems, copy-on-write
* **Network programming** — virtual networking, iptables, bridge networking
* **Demonstrate staff-level expertise** — this is the "wow factor" project

<Warning>
  This is the most challenging project in the course. You'll need:

  * Linux experience (kernel concepts, syscalls)
  * Systems programming knowledge
  * Understanding of networking fundamentals
  * Familiarity with Docker as a user
</Warning>

***

## Container Architecture Deep Dive

<Frame>
  <img src="https://mintcdn.com/devweeekends/emzPt-9B_R8UKdqm/images/courses/docker-container-architecture.svg?fit=max&auto=format&n=emzPt-9B_R8UKdqm&q=85&s=5cc12aa40ab8ec365b025376a9b2aef5" alt="Docker Container Architecture" width="1080" height="1080" data-path="images/courses/docker-container-architecture.svg" />
</Frame>

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                        CONTAINER RUNTIME ARCHITECTURE                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   USER SPACE                    KERNEL SPACE                                │
│   ──────────                    ────────────                                │
│                                                                              │
│   ┌───────────────┐             ┌────────────────────────────────────────┐ │
│   │  CLI (docker) │             │              LINUX KERNEL               │ │
│   └───────┬───────┘             │                                        │ │
│           │                     │  ┌─────────────┐  ┌─────────────────┐  │ │
│   ┌───────▼───────┐             │  │ NAMESPACES  │  │     CGROUPS     │  │ │
│   │   Daemon      │◄───────────►│  │ ─────────── │  │ ───────────────  │  │ │
│   │ (containerd)  │             │  │ • PID       │  │ • cpu           │  │ │
│   └───────┬───────┘             │  │ • NET       │  │ • memory        │  │ │
│           │                     │  │ • MNT       │  │ • blkio         │  │ │
│   ┌───────▼───────┐             │  │ • UTS       │  │ • pids          │  │ │
│   │   Runtime     │             │  │ • USER      │  │                 │  │ │
│   │    (runc)     │             │  │ • IPC       │  │                 │  │ │
│   └───────┬───────┘             │  └─────────────┘  └─────────────────┘  │ │
│           │                     │                                        │ │
│   ┌───────▼───────┐             │  ┌─────────────────────────────────┐  │ │
│   │   Container   │◄───────────►│  │         OVERLAY FS              │  │ │
│   │   Process     │             │  │  ┌─────┐  ┌─────┐  ┌─────┐      │  │ │
│   └───────────────┘             │  │  │Upper│  │Layer│  │Base │      │  │ │
│                                 │  │  │     │◄─│  2  │◄─│Image│      │  │ │
│                                 │  │  └─────┘  └─────┘  └─────┘      │  │ │
│                                 │  └─────────────────────────────────┘  │ │
│                                 └────────────────────────────────────────┘ │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

***

## What You'll Build

### Core Features

| Feature                  | Description          | Linux Concept         |
| ------------------------ | -------------------- | --------------------- |
| **PID Namespace**        | Process isolation    | `CLONE_NEWPID`        |
| **Mount Namespace**      | Filesystem isolation | `CLONE_NEWNS`         |
| **Network Namespace**    | Network isolation    | `CLONE_NEWNET`        |
| **UTS Namespace**        | Hostname isolation   | `CLONE_NEWUTS`        |
| **User Namespace**       | User/group isolation | `CLONE_NEWUSER`       |
| **Cgroups**              | Resource limits      | `/sys/fs/cgroup`      |
| **Overlay FS**           | Layered filesystem   | `mount -t overlay`    |
| **Container Networking** | Bridge, veth pairs   | `ip link`, `iptables` |
| **Image Format**         | OCI image spec       | Layers, manifests     |

***

## Interview Deep-Dive

<AccordionGroup>
  <Accordion title="What actually happens under the hood when Docker creates a container? Walk me through the kernel-level operations.">
    **Strong Answer:**

    * The container runtime (runc) makes a sequence of syscalls. First, it calls `clone()` or `unshare()` with namespace flags -- `CLONE_NEWPID`, `CLONE_NEWNS`, `CLONE_NEWNET`, `CLONE_NEWUTS`, `CLONE_NEWIPC`, and optionally `CLONE_NEWUSER`. Each flag creates a new namespace that gives the child process an isolated view of that particular resource. The process now sees itself as PID 1, has its own hostname, its own mount table, and its own network stack.
    * Next, the runtime sets up cgroups by writing to files under `/sys/fs/cgroup`. For example, writing `50000 100000` to `cpu.max` gives the container 50% of one CPU core. Writing a byte count to `memory.max` sets the hard memory limit. The container's PID is written to `cgroup.procs` to place it under these limits.
    * The filesystem is assembled using OverlayFS. The runtime mounts an overlay with the image layers as read-only lower directories, an empty upper directory for writes, and a work directory for atomic operations. Then it calls `pivot_root()` to atomically swap the container's root filesystem, which is more secure than `chroot()` because it fully detaches the old root.
    * For networking, the runtime creates a veth pair -- two virtual interfaces connected like a pipe. One end goes into the container's network namespace (becomes `eth0`), the other stays in the host namespace and attaches to a bridge (like `docker0`). NAT rules via iptables MASQUERADE enable outbound connectivity, and DNAT rules handle port forwarding.
    * Finally, the runtime calls `execve()` to replace itself with the container's entrypoint process. At this point, the container is running.

    **Follow-up: You mentioned pivot\_root is more secure than chroot. Why exactly?**

    `chroot()` only changes the apparent root directory for pathname lookups, but the process retains references to the old root via open file descriptors and the `..` trick (if it has root privileges, it can `chroot(".")` then `chdir("..")` repeatedly to escape). `pivot_root()` is an atomic operation that moves the old root to a subdirectory of the new root, and the runtime immediately unmounts and removes that directory. After `pivot_root`, there is no accessible path back to the host filesystem. This is why every serious container runtime uses `pivot_root` rather than `chroot` -- the attack surface is meaningfully smaller. In fact, container escape CVEs have historically exploited situations where this was not done correctly, such as CVE-2019-5736 in runc where a malicious container could overwrite the host runc binary.
  </Accordion>

  <Accordion title="Containers are often described as 'lightweight virtual machines.' Why is that description misleading, and what are the real security implications?">
    **Strong Answer:**

    * The description is misleading because VMs provide hardware-level isolation through a hypervisor, while containers share the host kernel. A VM has its own kernel, its own device drivers, and communicates with hardware through a hypervisor that mediates all access. A container is just a process with restricted views of the host kernel's resources.
    * The security implication is that containers share attack surface with the host kernel. A kernel exploit inside a container can potentially escape to the host because the container is running on the same kernel. With a VM, a kernel exploit only compromises the guest kernel -- the hypervisor is a separate, much smaller attack surface.
    * Real-world example: the Dirty COW vulnerability (CVE-2016-5195) was a Linux kernel race condition that allowed privilege escalation. Inside a VM, this only affected the guest. Inside a container, it could be used to escape to the host because the container shared the vulnerable kernel.
    * That said, containers have significantly improved their security posture over time. Seccomp profiles restrict which syscalls a container can make (Docker's default profile blocks \~44 syscalls). AppArmor and SELinux provide mandatory access control. User namespaces map container root to an unprivileged host user. These layers together provide defense in depth, but they are fundamentally different from the hardware isolation boundary of a hypervisor.
    * The practical takeaway is that containers are appropriate for workload isolation within a trust boundary (your own microservices), but not for running mutually untrusted code (that is what gVisor and Kata Containers address, by adding a kernel-level boundary back into the picture).

    **Follow-up: How do Kubernetes and cloud providers deal with this shared-kernel risk in multi-tenant environments?**

    Most major cloud providers (AWS EKS Fargate, GCP GKE Autopilot) run each customer's pods inside a lightweight VM -- essentially combining the container developer experience with VM-level isolation. AWS Firecracker is a microVM that boots in \~125ms and provides a KVM-based isolation boundary around each container. Google's gVisor takes a different approach: it implements a user-space kernel (Sentry) that intercepts syscalls from the container, so the container never talks directly to the host kernel. The trade-off is performance -- gVisor adds syscall overhead, Firecracker adds boot time and memory overhead. In practice, this means that the "containers vs. VMs" framing is outdated; modern infrastructure uses both, layered together, with the choice depending on the threat model.
  </Accordion>

  <Accordion title="Explain the noisy neighbor problem in containerized environments and how cgroups address it. What can still go wrong?">
    **Strong Answer:**

    * The noisy neighbor problem occurs when one container on a shared host consumes disproportionate resources, degrading performance for other containers. Without limits, a single container running a fork bomb or a memory leak can starve every other workload on the machine.
    * Cgroups address this by enforcing hard limits on CPU, memory, I/O bandwidth, and process count. The `cpu.max` file controls bandwidth allocation (e.g., 50000/100000 means 50% of one core). `memory.max` sets a hard ceiling -- if a process exceeds it, the kernel's OOM killer terminates it. `pids.max` prevents fork bombs by capping the number of processes.
    * What can still go wrong: cgroups do not limit *everything*. Kernel resources that are not cgroup-aware remain shared. For example, the dentry cache (filesystem metadata) and inode cache are global kernel structures. A container doing millions of file operations can bloat these caches and cause memory pressure for the entire host. Similarly, cgroups v1 did not limit kernel memory by default, so a container could exhaust kernel stack pages.
    * Another subtle issue is CPU throttling. CFS (Completely Fair Scheduler) bandwidth control with `cpu.max` can cause latency spikes even when the container is well below its quota. If a container uses its entire quota in the first 5ms of a 100ms period, it gets throttled for the remaining 95ms. This is why latency-sensitive applications (like API servers) sometimes see p99 latency spikes that correlate with cgroup throttling periods, not with actual load.
    * In production, I would also set `memory.high` (the soft limit that triggers throttling before the hard kill) and use CPU pinning (`cpuset.cpus`) for latency-critical workloads to avoid cache line bouncing across cores.

    **Follow-up: How would you debug a situation where a container is being OOM-killed but the application's memory usage looks normal in your monitoring?**

    This is a classic gotcha. Application-level metrics (like Go's `runtime.MemStats` or JVM heap usage) only show user-space allocations. The kernel counts *all* memory charged to the cgroup, including page cache, tmpfs mounts, kernel stack pages, and slab allocations. A container writing heavily to an in-container tmpfs (like `/dev/shm`) will consume memory that shows up in `memory.current` but not in application metrics. The debugging steps: check `memory.stat` in the cgroup directory (it breaks down RSS, cache, kernel stack, etc.), compare `memory.current` against `memory.max`, and look at `memory.events` for `oom_kill` counters. Also check if the container is doing heavy filesystem I/O to overlay-mounted paths, because those pages get charged to the cgroup's page cache.
  </Accordion>
</AccordionGroup>

***

## Implementation: Java

<Note>
  Java might seem unusual for container runtime development, but it demonstrates that containers aren't magic and can be implemented in any language with proper syscall access. We'll use JNI (Java Native Interface) to access Linux syscalls.
</Note>

### Project Structure

```
mydocker/
├── src/main/java/com/mydocker/
│   ├── MyDocker.java
│   ├── cli/
│   │   ├── CLI.java
│   │   ├── RunCommand.java
│   │   ├── PsCommand.java
│   │   └── ImagesCommand.java
│   ├── container/
│   │   ├── Container.java
│   │   ├── ContainerConfig.java
│   │   └── ContainerState.java
│   ├── runtime/
│   │   ├── Runtime.java
│   │   ├── Namespace.java
│   │   ├── Cgroup.java
│   │   └── Filesystem.java
│   ├── network/
│   │   ├── Network.java
│   │   ├── Bridge.java
│   │   └── VethPair.java
│   ├── image/
│   │   ├── Image.java
│   │   ├── Layer.java
│   │   └── Registry.java
│   └── native/
│       └── LinuxSyscalls.java
├── src/main/c/
│   └── syscalls.c
├── pom.xml
└── README.md
```

### Core Implementation

<CodeGroup>
  ```java container/Container.java theme={null}
  package com.mydocker.container;

  import java.nio.file.Path;
  import java.util.List;
  import java.util.Map;
  import java.util.UUID;

  /**
   * Represents a container instance
   */
  public class Container {
      private final String id;
      private final String name;
      private final ContainerConfig config;
      private ContainerState state;
      private int pid;
      private Path rootfs;
      
      public Container(ContainerConfig config) {
          this.id = UUID.randomUUID().toString().substring(0, 12);
          this.name = config.getName() != null ? config.getName() : "container_" + id.substring(0, 6);
          this.config = config;
          this.state = ContainerState.CREATED;
      }
      
      public String getId() { return id; }
      public String getName() { return name; }
      public ContainerConfig getConfig() { return config; }
      public ContainerState getState() { return state; }
      public int getPid() { return pid; }
      public Path getRootfs() { return rootfs; }
      
      public void setState(ContainerState state) { this.state = state; }
      public void setPid(int pid) { this.pid = pid; }
      public void setRootfs(Path rootfs) { this.rootfs = rootfs; }
  }

  /**
   * Container configuration
   */
  class ContainerConfig {
      private String image;
      private String name;
      private List<String> command;
      private Map<String, String> env;
      private String hostname;
      private boolean tty;
      private boolean interactive;
      private ResourceLimits resources;
      private NetworkConfig network;
      private List<String> volumes;
      
      // Builder pattern for configuration
      public static class Builder {
          private ContainerConfig config = new ContainerConfig();
          
          public Builder image(String image) { config.image = image; return this; }
          public Builder name(String name) { config.name = name; return this; }
          public Builder command(List<String> cmd) { config.command = cmd; return this; }
          public Builder env(Map<String, String> env) { config.env = env; return this; }
          public Builder hostname(String hostname) { config.hostname = hostname; return this; }
          public Builder tty(boolean tty) { config.tty = tty; return this; }
          public Builder interactive(boolean i) { config.interactive = i; return this; }
          public Builder resources(ResourceLimits r) { config.resources = r; return this; }
          public Builder network(NetworkConfig n) { config.network = n; return this; }
          public Builder volumes(List<String> v) { config.volumes = v; return this; }
          
          public ContainerConfig build() { return config; }
      }
      
      public String getImage() { return image; }
      public String getName() { return name; }
      public List<String> getCommand() { return command; }
      public Map<String, String> getEnv() { return env; }
      public String getHostname() { return hostname; }
      public boolean isTty() { return tty; }
      public boolean isInteractive() { return interactive; }
      public ResourceLimits getResources() { return resources; }
      public NetworkConfig getNetwork() { return network; }
      public List<String> getVolumes() { return volumes; }
  }

  /**
   * Resource limits using cgroups
   */
  class ResourceLimits {
      private long memoryLimit;      // bytes
      private long memorySwap;       // bytes
      private int cpuShares;         // relative weight
      private long cpuPeriod;        // microseconds
      private long cpuQuota;         // microseconds
      private int pidsLimit;         // max processes
      
      public long getMemoryLimit() { return memoryLimit; }
      public void setMemoryLimit(long limit) { this.memoryLimit = limit; }
      public int getCpuShares() { return cpuShares; }
      public void setCpuShares(int shares) { this.cpuShares = shares; }
      public long getCpuQuota() { return cpuQuota; }
      public void setCpuQuota(long quota) { this.cpuQuota = quota; }
      public int getPidsLimit() { return pidsLimit; }
      public void setPidsLimit(int limit) { this.pidsLimit = limit; }
  }

  /**
   * Network configuration
   */
  class NetworkConfig {
      private String mode;           // bridge, host, none
      private List<String> ports;    // port mappings
      private String ipAddress;
      
      public String getMode() { return mode; }
      public void setMode(String mode) { this.mode = mode; }
      public List<String> getPorts() { return ports; }
      public String getIpAddress() { return ipAddress; }
  }

  /**
   * Container state
   */
  enum ContainerState {
      CREATED,
      RUNNING,
      PAUSED,
      STOPPED,
      EXITED
  }
  ```

  ```java native/LinuxSyscalls.java theme={null}
  package com.mydocker.native;

  import java.io.*;
  import java.nio.file.*;

  /**
   * JNI wrapper for Linux syscalls needed for containers
   * 
   * In a real implementation, you'd compile the C code and load
   * the native library. For demonstration, we'll use process-based
   * alternatives where possible.
   */
  public class LinuxSyscalls {
      
      // Clone flags for namespaces
      public static final int CLONE_NEWPID = 0x20000000;
      public static final int CLONE_NEWNS  = 0x00020000;  // Mount namespace
      public static final int CLONE_NEWNET = 0x40000000;
      public static final int CLONE_NEWUTS = 0x04000000;
      public static final int CLONE_NEWIPC = 0x08000000;
      public static final int CLONE_NEWUSER = 0x10000000;
      
      // Mount flags
      public static final int MS_BIND = 4096;
      public static final int MS_PRIVATE = 1 << 18;
      public static final int MS_REC = 16384;
      
      static {
          try {
              System.loadLibrary("mydocker_native");
          } catch (UnsatisfiedLinkError e) {
              System.err.println("Warning: Native library not found, using process-based fallbacks");
          }
      }
      
      // Native methods (would be implemented in C)
      public native static int clone(int flags);
      public native static int unshare(int flags);
      public native static int setns(int fd, int nstype);
      public native static int mount(String source, String target, String fstype, long flags, String data);
      public native static int umount(String target);
      public native static int pivot_root(String new_root, String put_old);
      public native static int chroot(String path);
      public native static int sethostname(String name);
      public native static int setuid(int uid);
      public native static int setgid(int gid);
      
      /**
       * Execute unshare command to create namespaces
       * Fallback when native library isn't available
       */
      public static ProcessBuilder createNamespacedProcess(String... command) {
          // Use unshare command as fallback
          String[] unshareCmd = new String[command.length + 7];
          unshareCmd[0] = "unshare";
          unshareCmd[1] = "--pid";
          unshareCmd[2] = "--mount";
          unshareCmd[3] = "--uts";
          unshareCmd[4] = "--ipc";
          unshareCmd[5] = "--fork";
          unshareCmd[6] = "--mount-proc";
          System.arraycopy(command, 0, unshareCmd, 7, command.length);
          
          return new ProcessBuilder(unshareCmd);
      }
      
      /**
       * Create a cgroup for resource limits
       */
      public static void createCgroup(String name, String controller) throws IOException {
          Path cgroupPath = Paths.get("/sys/fs/cgroup", controller, "mydocker", name);
          Files.createDirectories(cgroupPath);
      }
      
      /**
       * Set cgroup limit
       */
      public static void setCgroupLimit(String name, String controller, String file, String value) 
              throws IOException {
          Path limitPath = Paths.get("/sys/fs/cgroup", controller, "mydocker", name, file);
          Files.writeString(limitPath, value);
      }
      
      /**
       * Add process to cgroup
       */
      public static void addToCgroup(String name, String controller, int pid) throws IOException {
          Path procsPath = Paths.get("/sys/fs/cgroup", controller, "mydocker", name, "cgroup.procs");
          Files.writeString(procsPath, String.valueOf(pid));
      }
      
      /**
       * Setup overlay filesystem
       */
      public static void mountOverlay(Path lowerDir, Path upperDir, Path workDir, Path mergedDir) 
              throws IOException {
          Files.createDirectories(upperDir);
          Files.createDirectories(workDir);
          Files.createDirectories(mergedDir);
          
          String options = String.format(
              "lowerdir=%s,upperdir=%s,workdir=%s",
              lowerDir.toAbsolutePath(),
              upperDir.toAbsolutePath(),
              workDir.toAbsolutePath()
          );
          
          ProcessBuilder pb = new ProcessBuilder(
              "mount", "-t", "overlay", "overlay",
              "-o", options,
              mergedDir.toAbsolutePath().toString()
          );
          
          try {
              Process p = pb.start();
              int exitCode = p.waitFor();
              if (exitCode != 0) {
                  throw new IOException("Failed to mount overlay filesystem");
              }
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
              throw new IOException("Mount interrupted", e);
          }
      }
  }
  ```

  ```java runtime/Namespace.java theme={null}
  package com.mydocker.runtime;

  import com.mydocker.native.LinuxSyscalls;
  import java.io.*;
  import java.nio.file.*;

  /**
   * Manages Linux namespaces for container isolation
   */
  public class Namespace {
      
      /**
       * Create and enter a new PID namespace
       * 
       * PID namespace isolates process IDs. The first process in a new
       * PID namespace becomes PID 1 (like init).
       */
      public static void enterPidNamespace() throws IOException {
          // Using unshare syscall (would be native in production)
          executeCommand("unshare", "--pid", "--fork", "--mount-proc");
      }
      
      /**
       * Create and enter a new mount namespace
       * 
       * Mount namespace isolates the filesystem mount table.
       * Changes to mounts in one namespace don't affect others.
       */
      public static void enterMountNamespace() throws IOException {
          executeCommand("unshare", "--mount");
      }
      
      /**
       * Create and enter a new network namespace
       * 
       * Network namespace provides a complete isolated network stack:
       * - Separate network devices
       * - IP addresses
       * - Routing table
       * - Firewall rules
       */
      public static void enterNetworkNamespace() throws IOException {
          executeCommand("unshare", "--net");
      }
      
      /**
       * Create and enter a new UTS namespace
       * 
       * UTS namespace isolates hostname and domain name.
       * Allows each container to have its own hostname.
       */
      public static void setupUtsNamespace(String hostname) throws IOException {
          // In new UTS namespace, set hostname
          executeCommand("hostname", hostname);
      }
      
      /**
       * Create and enter a new user namespace
       * 
       * User namespace maps user/group IDs between container and host.
       * Allows running as root inside container but unprivileged outside.
       */
      public static void setupUserNamespace(int containerUid, int hostUid) throws IOException {
          // Write UID mapping
          Path uidMap = Paths.get("/proc/self/uid_map");
          String mapping = String.format("%d %d 1\n", containerUid, hostUid);
          Files.writeString(uidMap, mapping);
          
          // Disable setgroups (required before writing gid_map)
          Path setgroups = Paths.get("/proc/self/setgroups");
          Files.writeString(setgroups, "deny\n");
          
          // Write GID mapping
          Path gidMap = Paths.get("/proc/self/gid_map");
          Files.writeString(gidMap, mapping);
      }
      
      /**
       * Setup isolated mount namespace with pivot_root
       * 
       * This is the key to filesystem isolation:
       * 1. Mount the container's root filesystem
       * 2. pivot_root to make it the new root
       * 3. Unmount the old root
       */
      public static void setupRootfs(Path containerRoot) throws IOException {
          Path putOld = containerRoot.resolve("oldrootfs");
          Files.createDirectories(putOld);
          
          // Make the mount private so changes don't propagate
          executeCommand("mount", "--make-rprivate", "/");
          
          // Bind mount the container root to itself (required for pivot_root)
          executeCommand("mount", "--bind", 
              containerRoot.toAbsolutePath().toString(),
              containerRoot.toAbsolutePath().toString()
          );
          
          // pivot_root: swap root filesystem
          // Old root goes to putOld, containerRoot becomes new root
          executeCommand("pivot_root", 
              containerRoot.toAbsolutePath().toString(),
              putOld.toAbsolutePath().toString()
          );
          
          // Change to new root
          System.setProperty("user.dir", "/");
          
          // Unmount old root
          executeCommand("umount", "-l", "/oldrootfs");
          
          // Remove the old root directory
          Files.deleteIfExists(Paths.get("/oldrootfs"));
      }
      
      /**
       * Mount essential filesystems inside the container
       */
      public static void mountEssentialFilesystems(Path containerRoot) throws IOException {
          // Mount /proc - provides process information
          Path proc = containerRoot.resolve("proc");
          Files.createDirectories(proc);
          executeCommand("mount", "-t", "proc", "proc", proc.toAbsolutePath().toString());
          
          // Mount /sys - provides kernel/hardware information
          Path sys = containerRoot.resolve("sys");
          Files.createDirectories(sys);
          executeCommand("mount", "-t", "sysfs", "sysfs", sys.toAbsolutePath().toString());
          
          // Mount /dev - device files
          Path dev = containerRoot.resolve("dev");
          Files.createDirectories(dev);
          executeCommand("mount", "-t", "tmpfs", "tmpfs", dev.toAbsolutePath().toString());
          
          // Create essential device nodes
          createDeviceNodes(dev);
          
          // Mount /dev/pts - pseudo-terminals
          Path devPts = dev.resolve("pts");
          Files.createDirectories(devPts);
          executeCommand("mount", "-t", "devpts", "devpts", devPts.toAbsolutePath().toString());
          
          // Mount /dev/shm - shared memory
          Path devShm = dev.resolve("shm");
          Files.createDirectories(devShm);
          executeCommand("mount", "-t", "tmpfs", "tmpfs", devShm.toAbsolutePath().toString());
      }
      
      private static void createDeviceNodes(Path dev) throws IOException {
          // Create null, zero, random, urandom, tty, console
          executeCommand("mknod", "-m", "666", dev.resolve("null").toString(), "c", "1", "3");
          executeCommand("mknod", "-m", "666", dev.resolve("zero").toString(), "c", "1", "5");
          executeCommand("mknod", "-m", "666", dev.resolve("random").toString(), "c", "1", "8");
          executeCommand("mknod", "-m", "666", dev.resolve("urandom").toString(), "c", "1", "9");
          executeCommand("mknod", "-m", "666", dev.resolve("tty").toString(), "c", "5", "0");
          executeCommand("mknod", "-m", "600", dev.resolve("console").toString(), "c", "5", "1");
          
          // Create symlinks
          Files.createSymbolicLink(dev.resolve("fd"), Paths.get("/proc/self/fd"));
          Files.createSymbolicLink(dev.resolve("stdin"), Paths.get("/proc/self/fd/0"));
          Files.createSymbolicLink(dev.resolve("stdout"), Paths.get("/proc/self/fd/1"));
          Files.createSymbolicLink(dev.resolve("stderr"), Paths.get("/proc/self/fd/2"));
      }
      
      private static void executeCommand(String... command) throws IOException {
          ProcessBuilder pb = new ProcessBuilder(command);
          pb.inheritIO();
          try {
              Process p = pb.start();
              int exitCode = p.waitFor();
              if (exitCode != 0) {
                  throw new IOException("Command failed with exit code " + exitCode + 
                      ": " + String.join(" ", command));
              }
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
              throw new IOException("Command interrupted", e);
          }
      }
  }
  ```

  ```java runtime/Cgroup.java theme={null}
  package com.mydocker.runtime;

  import com.mydocker.container.ContainerConfig;
  import java.io.*;
  import java.nio.file.*;

  /**
   * Manages cgroups for resource limits
   * 
   * Cgroups (control groups) limit and account for resource usage:
   * - CPU time
   * - Memory
   * - Disk I/O
   * - Network bandwidth
   * - Number of processes
   */
  public class Cgroup {
      
      // Cgroup v2 unified hierarchy path
      private static final Path CGROUP_ROOT = Paths.get("/sys/fs/cgroup");
      private static final String MYDOCKER_SLICE = "mydocker.slice";
      
      private final String containerId;
      private final Path cgroupPath;
      
      public Cgroup(String containerId) {
          this.containerId = containerId;
          this.cgroupPath = CGROUP_ROOT.resolve(MYDOCKER_SLICE).resolve(containerId);
      }
      
      /**
       * Create the cgroup for this container
       */
      public void create() throws IOException {
          // Create the cgroup directory
          Files.createDirectories(cgroupPath);
          
          // Enable controllers we need
          enableControllers();
      }
      
      /**
       * Apply resource limits from configuration
       */
      public void applyLimits(ContainerConfig config) throws IOException {
          var resources = config.getResources();
          if (resources == null) return;
          
          // Memory limit
          if (resources.getMemoryLimit() > 0) {
              setMemoryLimit(resources.getMemoryLimit());
          }
          
          // CPU limit
          if (resources.getCpuQuota() > 0) {
              setCpuLimit(resources.getCpuQuota());
          }
          
          // PIDs limit
          if (resources.getPidsLimit() > 0) {
              setPidsLimit(resources.getPidsLimit());
          }
      }
      
      /**
       * Set memory limit
       * 
       * memory.max: hard limit in bytes
       * memory.high: throttling limit (soft limit)
       */
      public void setMemoryLimit(long bytes) throws IOException {
          // Hard limit
          writeCgroupFile("memory.max", String.valueOf(bytes));
          
          // Soft limit at 90% of hard limit
          writeCgroupFile("memory.high", String.valueOf((long)(bytes * 0.9)));
      }
      
      /**
       * Set CPU limit using CPU quota/period
       * 
       * cpu.max: "quota period"
       * quota: microseconds of CPU time per period
       * period: length of period in microseconds (default 100000 = 100ms)
       * 
       * Example: "50000 100000" = 50% of one CPU
       *          "200000 100000" = 2 CPUs worth
       */
      public void setCpuLimit(long quotaMicros) throws IOException {
          String value = quotaMicros + " 100000";  // quota and 100ms period
          writeCgroupFile("cpu.max", value);
      }
      
      /**
       * Set PIDs limit
       * 
       * pids.max: maximum number of processes
       */
      public void setPidsLimit(int limit) throws IOException {
          writeCgroupFile("pids.max", String.valueOf(limit));
      }
      
      /**
       * Add a process to this cgroup
       */
      public void addProcess(int pid) throws IOException {
          writeCgroupFile("cgroup.procs", String.valueOf(pid));
      }
      
      /**
       * Get current memory usage
       */
      public long getMemoryUsage() throws IOException {
          String content = Files.readString(cgroupPath.resolve("memory.current")).trim();
          return Long.parseLong(content);
      }
      
      /**
       * Get current CPU usage statistics
       */
      public CpuStats getCpuStats() throws IOException {
          String content = Files.readString(cgroupPath.resolve("cpu.stat"));
          CpuStats stats = new CpuStats();
          
          for (String line : content.split("\n")) {
              String[] parts = line.split(" ");
              if (parts.length == 2) {
                  switch (parts[0]) {
                      case "usage_usec":
                          stats.usageUsec = Long.parseLong(parts[1]);
                          break;
                      case "user_usec":
                          stats.userUsec = Long.parseLong(parts[1]);
                          break;
                      case "system_usec":
                          stats.systemUsec = Long.parseLong(parts[1]);
                          break;
                  }
              }
          }
          
          return stats;
      }
      
      /**
       * Remove the cgroup
       */
      public void remove() throws IOException {
          // First, kill all processes in the cgroup
          try {
              String procs = Files.readString(cgroupPath.resolve("cgroup.procs"));
              for (String pidStr : procs.split("\n")) {
                  if (!pidStr.isEmpty()) {
                      int pid = Integer.parseInt(pidStr.trim());
                      Runtime.getRuntime().exec(new String[]{"kill", "-9", String.valueOf(pid)});
                  }
              }
          } catch (IOException e) {
              // Ignore - cgroup might be empty
          }
          
          // Wait a bit for processes to die
          try { Thread.sleep(100); } catch (InterruptedException e) {}
          
          // Remove the cgroup directory
          Files.deleteIfExists(cgroupPath);
      }
      
      private void enableControllers() throws IOException {
          // In cgroup v2, we need to enable controllers at the parent level
          Path parentSubtreeControl = cgroupPath.getParent().resolve("cgroup.subtree_control");
          
          if (Files.exists(parentSubtreeControl)) {
              // Enable memory, cpu, and pids controllers
              Files.writeString(parentSubtreeControl, "+memory +cpu +pids\n");
          }
      }
      
      private void writeCgroupFile(String filename, String value) throws IOException {
          Path file = cgroupPath.resolve(filename);
          Files.writeString(file, value);
      }
      
      /**
       * CPU statistics holder
       */
      public static class CpuStats {
          public long usageUsec;
          public long userUsec;
          public long systemUsec;
      }
  }
  ```

  ```java runtime/Filesystem.java theme={null}
  package com.mydocker.runtime;

  import com.mydocker.image.Image;
  import com.mydocker.image.Layer;
  import java.io.*;
  import java.nio.file.*;
  import java.util.*;

  /**
   * Manages container filesystem using overlay filesystem
   * 
   * Overlay FS allows stacking multiple directories:
   * - lowerdir: read-only base layers (image layers)
   * - upperdir: read-write layer (container changes)
   * - workdir: internal work directory
   * - merged: the combined view seen by the container
   */
  public class Filesystem {
      
      private static final Path CONTAINER_ROOT = Paths.get("/var/lib/mydocker/containers");
      private static final Path IMAGE_ROOT = Paths.get("/var/lib/mydocker/images");
      
      private final String containerId;
      private final Path containerDir;
      private final Path rootfs;
      private final Path upperDir;
      private final Path workDir;
      
      public Filesystem(String containerId) {
          this.containerId = containerId;
          this.containerDir = CONTAINER_ROOT.resolve(containerId);
          this.rootfs = containerDir.resolve("rootfs");
          this.upperDir = containerDir.resolve("upper");
          this.workDir = containerDir.resolve("work");
      }
      
      /**
       * Setup the overlay filesystem for a container
       */
      public Path setup(Image image) throws IOException {
          // Create container directories
          Files.createDirectories(containerDir);
          Files.createDirectories(rootfs);
          Files.createDirectories(upperDir);
          Files.createDirectories(workDir);
          
          // Get lower directories from image layers
          List<Path> lowerDirs = new ArrayList<>();
          for (Layer layer : image.getLayers()) {
              lowerDirs.add(layer.getPath());
          }
          
          // Mount overlay filesystem
          mountOverlay(lowerDirs);
          
          return rootfs;
      }
      
      /**
       * Mount overlay filesystem
       */
      private void mountOverlay(List<Path> lowerDirs) throws IOException {
          // Build lowerdir option (layers in reverse order, bottom first)
          StringBuilder lowerOpt = new StringBuilder();
          for (int i = lowerDirs.size() - 1; i >= 0; i--) {
              if (lowerOpt.length() > 0) lowerOpt.append(":");
              lowerOpt.append(lowerDirs.get(i).toAbsolutePath());
          }
          
          String options = String.format(
              "lowerdir=%s,upperdir=%s,workdir=%s",
              lowerOpt.toString(),
              upperDir.toAbsolutePath(),
              workDir.toAbsolutePath()
          );
          
          ProcessBuilder pb = new ProcessBuilder(
              "mount", "-t", "overlay", "overlay",
              "-o", options,
              rootfs.toAbsolutePath().toString()
          );
          
          pb.inheritIO();
          
          try {
              Process p = pb.start();
              int exitCode = p.waitFor();
              if (exitCode != 0) {
                  throw new IOException("Failed to mount overlay: exit code " + exitCode);
              }
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
              throw new IOException("Mount interrupted", e);
          }
      }
      
      /**
       * Setup essential filesystem structure inside container
       */
      public void setupContainerFs() throws IOException {
          // Create essential directories
          Files.createDirectories(rootfs.resolve("proc"));
          Files.createDirectories(rootfs.resolve("sys"));
          Files.createDirectories(rootfs.resolve("dev"));
          Files.createDirectories(rootfs.resolve("tmp"));
          Files.createDirectories(rootfs.resolve("run"));
          Files.createDirectories(rootfs.resolve("etc"));
          
          // Create /etc/hostname
          Files.writeString(rootfs.resolve("etc/hostname"), containerId.substring(0, 12) + "\n");
          
          // Create /etc/hosts
          String hosts = """
              127.0.0.1   localhost
              ::1         localhost ip6-localhost ip6-loopback
              """;
          Files.writeString(rootfs.resolve("etc/hosts"), hosts);
          
          // Create /etc/resolv.conf (copy from host)
          Path hostResolv = Paths.get("/etc/resolv.conf");
          if (Files.exists(hostResolv)) {
              Files.copy(hostResolv, rootfs.resolve("etc/resolv.conf"), 
                  StandardCopyOption.REPLACE_EXISTING);
          }
      }
      
      /**
       * Mount a volume into the container
       */
      public void mountVolume(String hostPath, String containerPath) throws IOException {
          Path target = rootfs.resolve(containerPath.startsWith("/") ? 
              containerPath.substring(1) : containerPath);
          
          Files.createDirectories(target);
          
          ProcessBuilder pb = new ProcessBuilder(
              "mount", "--bind", hostPath, target.toAbsolutePath().toString()
          );
          
          pb.inheritIO();
          
          try {
              Process p = pb.start();
              p.waitFor();
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
          }
      }
      
      /**
       * Cleanup filesystem (unmount and remove)
       */
      public void cleanup() throws IOException {
          // Unmount overlay
          try {
              new ProcessBuilder("umount", rootfs.toAbsolutePath().toString())
                  .inheritIO()
                  .start()
                  .waitFor();
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
          }
          
          // Remove container directory
          deleteRecursively(containerDir);
      }
      
      /**
       * Get changes made in the container (upper layer contents)
       */
      public List<String> getChanges() throws IOException {
          List<String> changes = new ArrayList<>();
          
          Files.walk(upperDir)
              .filter(p -> !p.equals(upperDir))
              .forEach(p -> {
                  String relativePath = upperDir.relativize(p).toString();
                  if (Files.isDirectory(p)) {
                      changes.add("A /" + relativePath + "/");
                  } else {
                      changes.add("A /" + relativePath);
                  }
              });
          
          return changes;
      }
      
      public Path getRootfs() {
          return rootfs;
      }
      
      private void deleteRecursively(Path path) throws IOException {
          if (Files.exists(path)) {
              Files.walk(path)
                  .sorted(Comparator.reverseOrder())
                  .forEach(p -> {
                      try {
                          Files.delete(p);
                      } catch (IOException e) {
                          // Ignore
                      }
                  });
          }
      }
  }
  ```

  ```java network/Network.java theme={null}
  package com.mydocker.network;

  import java.io.*;
  import java.nio.file.*;
  import java.util.*;

  /**
   * Container networking implementation
   * 
   * Creates a bridge network similar to Docker's default bridge:
   * - Bridge device on host (mydocker0)
   * - Veth pair connecting container to bridge
   * - NAT rules for outbound traffic
   * - Port forwarding for inbound traffic
   */
  public class Network {
      
      private static final String BRIDGE_NAME = "mydocker0";
      private static final String BRIDGE_IP = "172.17.0.1";
      private static final String BRIDGE_SUBNET = "172.17.0.0/16";
      private static final int IP_POOL_START = 2;  // Start at 172.17.0.2
      
      private static int nextIp = IP_POOL_START;
      
      /**
       * Initialize the default bridge network
       */
      public static void initBridge() throws IOException {
          // Check if bridge already exists
          if (bridgeExists()) {
              return;
          }
          
          // Create bridge device
          executeIp("link", "add", BRIDGE_NAME, "type", "bridge");
          
          // Assign IP address
          executeIp("addr", "add", BRIDGE_IP + "/16", "dev", BRIDGE_NAME);
          
          // Bring bridge up
          executeIp("link", "set", BRIDGE_NAME, "up");
          
          // Enable IP forwarding
          Files.writeString(Paths.get("/proc/sys/net/ipv4/ip_forward"), "1");
          
          // Add NAT rule for outbound traffic
          execute("iptables", "-t", "nat", "-A", "POSTROUTING", 
              "-s", BRIDGE_SUBNET, "-j", "MASQUERADE");
      }
      
      /**
       * Connect a container to the bridge network
       * 
       * @param containerPid The PID of the container's init process
       * @param containerId The container ID (for naming)
       * @return The assigned IP address
       */
      public static String connect(int containerPid, String containerId) throws IOException {
          String vethHost = "veth" + containerId.substring(0, 6);
          String vethContainer = "eth0";
          String containerIp = "172.17.0." + (nextIp++);
          
          // Create veth pair
          executeIp("link", "add", vethHost, "type", "veth", "peer", "name", vethContainer);
          
          // Attach host end to bridge
          executeIp("link", "set", vethHost, "master", BRIDGE_NAME);
          executeIp("link", "set", vethHost, "up");
          
          // Move container end to container's network namespace
          executeIp("link", "set", vethContainer, "netns", String.valueOf(containerPid));
          
          // Configure container's network namespace
          executeNsenter(containerPid, "ip", "addr", "add", containerIp + "/16", "dev", vethContainer);
          executeNsenter(containerPid, "ip", "link", "set", vethContainer, "up");
          executeNsenter(containerPid, "ip", "link", "set", "lo", "up");
          executeNsenter(containerPid, "ip", "route", "add", "default", "via", BRIDGE_IP);
          
          return containerIp;
      }
      
      /**
       * Add port mapping from host to container
       * 
       * @param hostPort Port on the host
       * @param containerIp Container's IP address
       * @param containerPort Port in the container
       */
      public static void addPortMapping(int hostPort, String containerIp, int containerPort) 
              throws IOException {
          // DNAT rule to forward incoming traffic
          execute("iptables", "-t", "nat", "-A", "PREROUTING",
              "-p", "tcp", "--dport", String.valueOf(hostPort),
              "-j", "DNAT", "--to-destination", containerIp + ":" + containerPort);
          
          // Also handle local traffic
          execute("iptables", "-t", "nat", "-A", "OUTPUT",
              "-p", "tcp", "--dport", String.valueOf(hostPort),
              "-j", "DNAT", "--to-destination", containerIp + ":" + containerPort);
      }
      
      /**
       * Remove port mapping
       */
      public static void removePortMapping(int hostPort, String containerIp, int containerPort) 
              throws IOException {
          execute("iptables", "-t", "nat", "-D", "PREROUTING",
              "-p", "tcp", "--dport", String.valueOf(hostPort),
              "-j", "DNAT", "--to-destination", containerIp + ":" + containerPort);
          
          execute("iptables", "-t", "nat", "-D", "OUTPUT",
              "-p", "tcp", "--dport", String.valueOf(hostPort),
              "-j", "DNAT", "--to-destination", containerIp + ":" + containerPort);
      }
      
      /**
       * Disconnect container from network
       */
      public static void disconnect(String containerId) throws IOException {
          String vethHost = "veth" + containerId.substring(0, 6);
          
          // Deleting the host end also deletes the container end
          try {
              executeIp("link", "delete", vethHost);
          } catch (IOException e) {
              // Ignore if already deleted
          }
      }
      
      private static boolean bridgeExists() {
          try {
              ProcessBuilder pb = new ProcessBuilder("ip", "link", "show", BRIDGE_NAME);
              return pb.start().waitFor() == 0;
          } catch (Exception e) {
              return false;
          }
      }
      
      private static void executeIp(String... args) throws IOException {
          String[] cmd = new String[args.length + 1];
          cmd[0] = "ip";
          System.arraycopy(args, 0, cmd, 1, args.length);
          execute(cmd);
      }
      
      private static void executeNsenter(int pid, String... args) throws IOException {
          String[] cmd = new String[args.length + 4];
          cmd[0] = "nsenter";
          cmd[1] = "-t";
          cmd[2] = String.valueOf(pid);
          cmd[3] = "-n";
          System.arraycopy(args, 0, cmd, 4, args.length);
          execute(cmd);
      }
      
      private static void execute(String... cmd) throws IOException {
          ProcessBuilder pb = new ProcessBuilder(cmd);
          pb.inheritIO();
          
          try {
              Process p = pb.start();
              int exitCode = p.waitFor();
              if (exitCode != 0) {
                  throw new IOException("Command failed: " + String.join(" ", cmd));
              }
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
              throw new IOException("Command interrupted", e);
          }
      }
  }
  ```

  ```java runtime/Runtime.java theme={null}
  package com.mydocker.runtime;

  import com.mydocker.container.*;
  import com.mydocker.image.*;
  import com.mydocker.network.Network;
  import java.io.*;
  import java.nio.file.*;
  import java.util.*;
  import java.util.concurrent.*;

  /**
   * Container runtime - orchestrates container lifecycle
   */
  public class Runtime {
      
      private static final Path RUNTIME_ROOT = Paths.get("/var/lib/mydocker");
      private static final Path CONTAINERS_PATH = RUNTIME_ROOT.resolve("containers");
      
      private final Map<String, Container> containers = new ConcurrentHashMap<>();
      private final ImageStore imageStore;
      
      public Runtime() {
          this.imageStore = new ImageStore();
          initializeRuntime();
      }
      
      private void initializeRuntime() {
          try {
              Files.createDirectories(CONTAINERS_PATH);
              Network.initBridge();
          } catch (IOException e) {
              throw new RuntimeException("Failed to initialize runtime", e);
          }
      }
      
      /**
       * Create and start a container
       */
      public Container run(ContainerConfig config) throws IOException {
          // 1. Pull image if needed
          Image image = imageStore.getOrPull(config.getImage());
          
          // 2. Create container
          Container container = new Container(config);
          containers.put(container.getId(), container);
          
          // 3. Setup filesystem
          Filesystem fs = new Filesystem(container.getId());
          Path rootfs = fs.setup(image);
          fs.setupContainerFs();
          container.setRootfs(rootfs);
          
          // 4. Setup cgroups
          Cgroup cgroup = new Cgroup(container.getId());
          cgroup.create();
          cgroup.applyLimits(config);
          
          // 5. Start container process
          int pid = startContainerProcess(container, rootfs);
          container.setPid(pid);
          container.setState(ContainerState.RUNNING);
          
          // 6. Add process to cgroup
          cgroup.addProcess(pid);
          
          // 7. Setup networking
          if (!"none".equals(config.getNetwork().getMode())) {
              String ip = Network.connect(pid, container.getId());
              config.getNetwork().setIpAddress(ip);
              
              // Setup port mappings
              for (String mapping : config.getNetwork().getPorts()) {
                  String[] parts = mapping.split(":");
                  int hostPort = Integer.parseInt(parts[0]);
                  int containerPort = Integer.parseInt(parts[1]);
                  Network.addPortMapping(hostPort, ip, containerPort);
              }
          }
          
          return container;
      }
      
      private int startContainerProcess(Container container, Path rootfs) throws IOException {
          ContainerConfig config = container.getConfig();
          List<String> command = config.getCommand();
          
          // Build the command with unshare for namespace isolation
          List<String> fullCommand = new ArrayList<>();
          fullCommand.add("unshare");
          fullCommand.add("--pid");
          fullCommand.add("--mount");
          fullCommand.add("--uts");
          fullCommand.add("--ipc");
          fullCommand.add("--fork");
          fullCommand.add("--root=" + rootfs.toAbsolutePath());
          fullCommand.add("--mount-proc");
          
          // Set hostname if specified
          if (config.getHostname() != null) {
              fullCommand.add("--uts");
          }
          
          fullCommand.addAll(command);
          
          ProcessBuilder pb = new ProcessBuilder(fullCommand);
          
          // Set environment variables
          Map<String, String> env = pb.environment();
          env.clear();
          env.put("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin");
          env.put("TERM", "xterm");
          env.put("HOME", "/root");
          
          if (config.getEnv() != null) {
              env.putAll(config.getEnv());
          }
          
          if (config.isTty() || config.isInteractive()) {
              pb.inheritIO();
          }
          
          Process process = pb.start();
          
          // Get the actual PID (the unshare process)
          return (int) process.pid();
      }
      
      /**
       * Stop a running container
       */
      public void stop(String containerId) throws IOException {
          Container container = containers.get(containerId);
          if (container == null) {
              throw new IllegalArgumentException("Container not found: " + containerId);
          }
          
          // Send SIGTERM to container process
          ProcessBuilder pb = new ProcessBuilder("kill", "-15", String.valueOf(container.getPid()));
          pb.start().waitFor(10, TimeUnit.SECONDS);
          
          container.setState(ContainerState.STOPPED);
      }
      
      /**
       * Remove a container
       */
      public void remove(String containerId) throws IOException {
          Container container = containers.get(containerId);
          if (container == null) {
              throw new IllegalArgumentException("Container not found: " + containerId);
          }
          
          // Force stop if running
          if (container.getState() == ContainerState.RUNNING) {
              ProcessBuilder pb = new ProcessBuilder("kill", "-9", String.valueOf(container.getPid()));
              try {
                  pb.start().waitFor();
              } catch (InterruptedException e) {
                  Thread.currentThread().interrupt();
              }
          }
          
          // Cleanup networking
          Network.disconnect(containerId);
          
          // Cleanup cgroup
          Cgroup cgroup = new Cgroup(containerId);
          cgroup.remove();
          
          // Cleanup filesystem
          Filesystem fs = new Filesystem(containerId);
          fs.cleanup();
          
          containers.remove(containerId);
      }
      
      /**
       * List all containers
       */
      public List<Container> list() {
          return new ArrayList<>(containers.values());
      }
      
      /**
       * Get container by ID
       */
      public Container get(String containerId) {
          return containers.get(containerId);
      }
      
      /**
       * Execute command in running container
       */
      public int exec(String containerId, List<String> command) throws IOException {
          Container container = containers.get(containerId);
          if (container == null || container.getState() != ContainerState.RUNNING) {
              throw new IllegalStateException("Container not running: " + containerId);
          }
          
          // Use nsenter to enter container's namespaces
          List<String> nsenterCmd = new ArrayList<>();
          nsenterCmd.add("nsenter");
          nsenterCmd.add("-t");
          nsenterCmd.add(String.valueOf(container.getPid()));
          nsenterCmd.add("-m");  // mount namespace
          nsenterCmd.add("-u");  // UTS namespace
          nsenterCmd.add("-i");  // IPC namespace
          nsenterCmd.add("-n");  // network namespace
          nsenterCmd.add("-p");  // PID namespace
          nsenterCmd.addAll(command);
          
          ProcessBuilder pb = new ProcessBuilder(nsenterCmd);
          pb.inheritIO();
          
          try {
              Process p = pb.start();
              return p.waitFor();
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
              return -1;
          }
      }
  }
  ```

  ```java cli/CLI.java theme={null}
  package com.mydocker.cli;

  import com.mydocker.container.*;
  import com.mydocker.runtime.Runtime;
  import java.util.*;

  /**
   * Command-line interface for mydocker
   */
  public class CLI {
      
      private final Runtime runtime;
      
      public CLI() {
          this.runtime = new Runtime();
      }
      
      public void run(String[] args) {
          if (args.length == 0) {
              printUsage();
              return;
          }
          
          String command = args[0];
          String[] cmdArgs = Arrays.copyOfRange(args, 1, args.length);
          
          try {
              switch (command) {
                  case "run" -> handleRun(cmdArgs);
                  case "ps" -> handlePs(cmdArgs);
                  case "stop" -> handleStop(cmdArgs);
                  case "rm" -> handleRemove(cmdArgs);
                  case "exec" -> handleExec(cmdArgs);
                  case "images" -> handleImages(cmdArgs);
                  case "pull" -> handlePull(cmdArgs);
                  default -> {
                      System.err.println("Unknown command: " + command);
                      printUsage();
                  }
              }
          } catch (Exception e) {
              System.err.println("Error: " + e.getMessage());
              e.printStackTrace();
          }
      }
      
      private void handleRun(String[] args) throws Exception {
          ContainerConfig.Builder builder = new ContainerConfig.Builder();
          List<String> command = new ArrayList<>();
          
          int i = 0;
          while (i < args.length) {
              switch (args[i]) {
                  case "-d", "--detach" -> {
                      // Run in background
                      i++;
                  }
                  case "-it" -> {
                      builder.tty(true).interactive(true);
                      i++;
                  }
                  case "--name" -> {
                      builder.name(args[++i]);
                      i++;
                  }
                  case "-e", "--env" -> {
                      String[] env = args[++i].split("=", 2);
                      builder.env(Map.of(env[0], env.length > 1 ? env[1] : ""));
                      i++;
                  }
                  case "-m", "--memory" -> {
                      long mem = parseMemory(args[++i]);
                      ResourceLimits limits = new ResourceLimits();
                      limits.setMemoryLimit(mem);
                      builder.resources(limits);
                      i++;
                  }
                  case "-p", "--publish" -> {
                      NetworkConfig net = new NetworkConfig();
                      net.setMode("bridge");
                      net.setPorts(List.of(args[++i]));
                      builder.network(net);
                      i++;
                  }
                  default -> {
                      if (!args[i].startsWith("-")) {
                          builder.image(args[i]);
                          command.addAll(Arrays.asList(Arrays.copyOfRange(args, i + 1, args.length)));
                          i = args.length;
                      } else {
                          i++;
                      }
                  }
              }
          }
          
          if (command.isEmpty()) {
              command.add("/bin/sh");
          }
          builder.command(command);
          
          Container container = runtime.run(builder.build());
          System.out.println(container.getId());
      }
      
      private void handlePs(String[] args) {
          boolean all = Arrays.asList(args).contains("-a");
          
          System.out.printf("%-12s %-20s %-15s %-10s %-20s%n",
              "CONTAINER ID", "IMAGE", "COMMAND", "STATUS", "NAMES");
          
          for (Container c : runtime.list()) {
              if (!all && c.getState() != ContainerState.RUNNING) continue;
              
              String cmd = String.join(" ", c.getConfig().getCommand());
              if (cmd.length() > 15) cmd = cmd.substring(0, 12) + "...";
              
              System.out.printf("%-12s %-20s %-15s %-10s %-20s%n",
                  c.getId().substring(0, 12),
                  c.getConfig().getImage(),
                  cmd,
                  c.getState().toString().toLowerCase(),
                  c.getName()
              );
          }
      }
      
      private void handleStop(String[] args) throws Exception {
          for (String id : args) {
              runtime.stop(id);
              System.out.println(id);
          }
      }
      
      private void handleRemove(String[] args) throws Exception {
          for (String id : args) {
              runtime.remove(id);
              System.out.println(id);
          }
      }
      
      private void handleExec(String[] args) throws Exception {
          if (args.length < 2) {
              System.err.println("Usage: mydocker exec CONTAINER COMMAND [ARG...]");
              return;
          }
          
          String containerId = args[0];
          List<String> command = Arrays.asList(Arrays.copyOfRange(args, 1, args.length));
          
          int exitCode = runtime.exec(containerId, command);
          System.exit(exitCode);
      }
      
      private void handleImages(String[] args) {
          System.out.printf("%-20s %-15s %-15s %-15s%n",
              "REPOSITORY", "TAG", "IMAGE ID", "SIZE");
          // List images from image store
      }
      
      private void handlePull(String[] args) {
          if (args.length < 1) {
              System.err.println("Usage: mydocker pull IMAGE[:TAG]");
              return;
          }
          // Pull image from registry
          System.out.println("Pulling " + args[0] + "...");
      }
      
      private long parseMemory(String s) {
          s = s.toLowerCase();
          long multiplier = 1;
          if (s.endsWith("g")) {
              multiplier = 1024 * 1024 * 1024;
              s = s.substring(0, s.length() - 1);
          } else if (s.endsWith("m")) {
              multiplier = 1024 * 1024;
              s = s.substring(0, s.length() - 1);
          } else if (s.endsWith("k")) {
              multiplier = 1024;
              s = s.substring(0, s.length() - 1);
          }
          return Long.parseLong(s) * multiplier;
      }
      
      private void printUsage() {
          System.out.println("""
              Usage: mydocker COMMAND [OPTIONS]
              
              Commands:
                run         Create and run a new container
                ps          List containers
                stop        Stop running containers
                rm          Remove containers
                exec        Execute a command in a running container
                images      List images
                pull        Pull an image from a registry
              
              Run 'mydocker COMMAND --help' for more information on a command.
              """);
      }
      
      public static void main(String[] args) {
          new CLI().run(args);
      }
  }
  ```
</CodeGroup>

***

## Testing Your Docker

```bash theme={null}
# Build
mvn package

# Run a container (requires root)
sudo java -jar target/mydocker.jar run -it alpine /bin/sh

# Inside container
/ # hostname
abc123def456

/ # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/sh
    2 root      0:00 ps aux

/ # cat /etc/resolv.conf
# Container has network access

/ # exit

# List containers
sudo java -jar target/mydocker.jar ps -a

# Remove container
sudo java -jar target/mydocker.jar rm abc123
```

***

## Advanced Exercises

### Level 1: Core Improvements

1. Implement proper OCI image format support
2. Add container logging (capture stdout/stderr)
3. Implement container restart policies

### Level 2: Production Features

1. Add seccomp filtering for security
2. Implement container health checks
3. Add volume mounting support

### Level 3: Orchestration

1. Implement basic networking between containers
2. Add container-to-container DNS resolution
3. Build a simple container orchestrator

***

## What You've Learned

<Check>Linux namespaces (PID, mount, network, UTS, user)</Check>
<Check>Cgroups for resource limits</Check>
<Check>Overlay filesystems and copy-on-write</Check>
<Check>Container networking (bridges, veth pairs, NAT)</Check>
<Check>OCI image format concepts</Check>
<Check>Container security mechanisms</Check>

***

## Resume Impact

With this project, you can confidently say:

> "Built a container runtime from scratch implementing Linux namespaces, cgroups, and overlay filesystems. Demonstrated deep understanding of kernel-level isolation, resource management, and container networking."

This immediately signals staff-level systems expertise.

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Go Implementation" icon="golang" href="/courses/build-your-own-x/docker-go">
    See the more common Go implementation approach
  </Card>

  <Card title="JavaScript Implementation" icon="js" href="/courses/build-your-own-x/docker-js">
    Node.js with native bindings approach
  </Card>

  <Card title="Contribute to containerd" icon="github" href="https://github.com/containerd/containerd">
    Take your skills to the actual project
  </Card>
</CardGroup>
