Skip to content

milanmedvec/workspace_archlinux_cm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 

Repository files navigation

Building a Minimal Container Manager on Top of runc

I built this container manager because I don’t want a Docker-like workflow when I just need simple process isolation per project.

In most cases, I don’t need:

  • image layering
  • registries
  • overlay filesystems
  • background daemons

What I want is much simpler:

Take my project directory, isolate it, and run it in a controlled environment.

That’s it.


Core idea

The tool (cm) is a thin wrapper around runc, with a very simple structure:

~/.local/share/cm/
  <project>/
    <bundle>/
      rootfs/
      config.json
  • Project = current directory name
  • Bundle = container definition
  • rootfs = extracted filesystem (Debian)
  • config.json = OCI spec

No abstractions beyond what runc already expects.


Why this approach

Instead of building images and layering filesystems, I:

  • keep my project on the host filesystem
  • bind-mount it into the container
  • run everything directly against it

No overlay, no copy, no sync step.

"destination": "/project",
"type": "none",
"source": $src,
"options": ["rbind", "rw"]

This makes the container behave like a lightweight sandbox around the current working directory.


Creating a bundle

cm create [subname]

This does a few things:

  1. Creates the bundle directory
  2. Exports a minimal Debian root filesystem
  3. Generates a rootless OCI config
  4. Patches it for actual use

The rootfs is created like this:

cid=$(docker create debian:latest)
docker export "$cid" | tar -C "$bundle/rootfs" -xf -
docker rm "$cid"

Docker is only used here as a convenient way to fetch a base filesystem.


Making the container usable

Raw runc spec isn’t enough, so I patch it:

  • working directory → /project
  • bind mount current directory
  • enable writeable rootfs
  • define UID/GID mappings
  • set minimal capabilities
.process.cwd = "/project"

This turns the container into a usable dev/runtime environment.


Running containers

cm run [subname] --memory 512m

Each run:

  • generates a unique container ID
  • optionally applies memory limits
  • executes via runc

Memory limits are injected dynamically:

.linux.resources.memory.limit = $limit

If a limit is used, a temporary bundle is created on the fly.


Cleanup

cm delete
cm delete <project>
cm delete <project> <bundle>

Before deleting anything, the tool:

  • finds related containers
  • stops them if needed
  • removes them cleanly

No leftover state.


Design decisions

No overlay filesystem

I don’t need layered filesystems for my use case.

The project directory is the source of truth, and the container just wraps it.


Rootless by default

Everything runs in user space:

{"containerID": 0, "hostID": 1000, "size": 1}

No daemon, no elevated privileges.


Minimal surface area

This tool intentionally avoids:

  • networking abstractions
  • image management
  • orchestration

It’s just:

filesystem + config + process


What this enables

  • per-project isolation without Docker overhead
  • running code in a clean environment instantly
  • simple, reproducible execution
  • experimentation with OCI without extra layers

Final thoughts

This approach strips containers down to what I actually need.

No images, no overlays, no orchestration—just a controlled environment around a directory.

It’s closer to how containers work underneath, and in many cases, that’s enough.

About

Minimal Container Manager on Top of runc

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages