Skip to content

Repository files navigation

MicroCommandControl (MC2)

MC2 is a Compose-shaped orchestrator for microsandbox microVMs. Declare services in a Docker-Compose-style stack file; MC2 schedules, runs, and continuously reconciles them as detached, hardware-virtualized microVMs on your machine — replicas, restart policies, health-gated startup ordering, encrypted secrets, a default-allow service network, and Traefik ingress, without reimplementing the VMM and without becoming Kubernetes.

One process, no daemon, no agent: mc2 server is the orchestrator — SQLite state, REST API, scheduler, and a reconcile loop that drives detached microVMs through the embedded microsandbox SDK.

Where it fits

MC2 Docker Compose Kubernetes Firecracker-style
Isolation microVM (kernel) per replica container (shared kernel) container microVM
Model desired-state reconcile run-once controllers imperative VMM
Scope single node, single process single host multi-node no orchestrator
Ergonomics Compose vocabulary Compose-native kubectl n/a

Container ergonomics on real microVM isolation, without Kubernetes.

Why MC2

microsandbox boots a fast microVM. MC2 is the orchestration layer on top of it — Compose-shaped stacks, lifecycle, networking, secrets, and operator tooling, the parts msb leaves to you.

  • Compose-shaped stacks. services, scale, ports, expose, environment, healthcheck, restart, depends_on, volumes, networks, ingress in a git-committable file. Unknown keys are rejected, not ignored.
  • Desired state, converged. mc2 up reconciles replicas, restart policies, and health-gated startup ordering (depends_on: {db: {condition: service_healthy}}); mc2 down (alias rm, add --volumes) tears down. An apply is all-or-nothing, and the file is the whole desired state: a service you delete from it is torn down by the next up. No run-once, no drift.
  • A local, private sandbox and app host. The embedded SDK keeps every microVM on your box (or a server you control) — no account or hosted service. mc2 exec and mc2 logs --follow are built in.
  • Secrets the guest never holds. A server-wide store (mc2 secret set), encrypted in MC2's database, that never echoes values back; the guest sees a placeholder and the real value is added only to TLS connections to allowHosts.
  • A service network, not a pod network. expose → default-allow east–west by name (<service>, or <service>.<stack>.svc.mc2), across stacks through server-wide named networks; ports + ingress: produce a Traefik catalog. mc2 network shows it all.
  • Per-replica ports, no bookkeeping. scale: 3 turns 8080 into 8080, 8081, 8082; target-only ports get stable auto host ports.
  • One process, one CLI. mc2 server = SQLite + REST + scheduler + a reconcile loop. The same commands work on your laptop or a remote server (https + token).

What's possible

  • Launch a swarm of agents. scale: 20 on an agent image puts each agent in its own hardware-isolated microVM; restart keeps them alive through crashes, and mc2 exec / mc2 logs --follow drop you into any of them. Give the swarm its own network so agents can coordinate — or keep them fully isolated.
  • Power your existing Docker Compose with microVMs. If you can write a docker-compose.yml, you already know MC2 — the same services, ports, depends_on, and environment shape now boots each service as a hardware-isolated microVM, with no image builds or platform teams.
  • Remote coding agents over SSH. ssh: true on a dev VM plus a key from mc2 ssh add-key gives it an SSH endpoint on the server's loopback; reach it through a tunnel or a Traefik TCP route.
  • A throwaway test grid. Bring up N identical VMs, run your suite across all of them, then mc2 down <stack> --volumes and they're gone — or keep a cache volume for reuse.
  • A fleet of headless browsers. Thirty scraper VMs, each disposable inside its own microVM, each with its own egress profile (public, private, or none).

Install

Prebuilt binaries for Linux (amd64/arm64) and macOS (Apple Silicon) ship with every release. Download the archive for your platform, check it against the published SHA-256, then install it:

VERSION=0.1.0                    # pin the version you verified
TARGET=aarch64-apple-darwin      # or x86_64-unknown-linux-gnu / aarch64-unknown-linux-gnu
BASE="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/l3wi/mc2/releases/download/v${VERSION}"
ARCHIVE="mc2-${TARGET}.tar.xz"

curl -fsSL -O "$BASE/$ARCHIVE" -O "$BASE/$ARCHIVE.sha256"

# Verify the download against the checksum published with the release.
# macOS: use `shasum -a 256 -c -` instead of `sha256sum -c -`.
grep -F "$ARCHIVE" "$ARCHIVE.sha256" | sha256sum -c -

# Optional: verify the build's provenance with GitHub Artifact Attestations.
# Requires the GitHub CLI; only releases published with attestations pass.
gh attestation verify "$ARCHIVE" --repo l3wi/mc2

tar -xJf "$ARCHIVE" && sudo install -m 0755 "mc2-${TARGET}/mc2" /usr/local/bin/mc2
mc2 --version

VERSION is pinned deliberately: releases/latest is a moving target, so an unpinned installer can silently replace the binary you verified.

Prefer a script? A shell installer is published too — download it, read it, then run it:

VERSION=0.1.0
curl -fsSLo mc2-installer.sh "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/l3wi/mc2/releases/download/v${VERSION}/mc2-installer.sh"
less mc2-installer.sh   # inspect before running
sh mc2-installer.sh

First run

Requirements: mc2 on your PATH (see Install) and a hypervisor (Linux KVM / Apple Silicon HVF) for running sandboxes. Check the host, then let the wizard collect flags and print the commands to finish:

mc2 doctor
mc2 setup

mc2 setup is interactive (needs a TTY). Pick a tree, or jump straight to one with mc2 setup server / mc2 setup client. The wizard only scaffolds config — it never starts the server or Traefik.

  1. On the machine that will run VMs choose Server. It asks for bind address, data dir, optional public hostname / Traefik, then auth, then prints a copy-paste mc2 server … command and a finish-setup checklist.
  2. Start the server with that command. Auth is on by default; the token is shown once on first bootstrap — stderr on a TTY, else <data-dir>/bootstrap-token (0600). Save it. (--no-auth runs this start open; loopback only unless you add --allow-unauthenticated-remote.)
  3. On your laptop (or the same machine) run mc2 setup again and choose Client. Paste the control-plane URL and token (leave the token blank for a --no-auth server); it saves a named context in ~/.mc2/config.toml (0600) and can verify the connection.
  4. Operate:
mc2 node ls

# There is no examples/ checkout after a binary-only install, so write the
# same minimal stack the example ships with (the name defaults to `hello`):
cat > hello.yaml <<'YAML'
services:
  web:
    image: python:3.12-alpine
    ports:
      - "18091:8000"
    command: ["/bin/sh", "-c", "mkdir -p /tmp/www && echo 'hello world' > /tmp/www/index.html && cd /tmp/www && exec python -m http.server 8000"]
YAML

mc2 up -f hello.yaml
mc2 ps            # repeat until hello/web/0 is Running
# Published ports bind the server's loopback; run this on the server host:
curl -fsS http://127.0.0.1:18091/

Full walkthrough: site/content/documentation/quickstart.mdx.

Skip the wizard (lab)

Two terminals, no context file, no auth. Defaults are data dir ~/.mc2 and bind 127.0.0.1:7443; the CLI already talks to that loopback URL.

# Terminal 1 — the orchestrator (one process)
# Loopback bind: --no-auth is fine here. The token still exists in the store.
mc2 server --no-auth

# Terminal 2 — operator
mc2 node ls
mc2 up -f hello.yaml   # the stack written above; examples/01-hello-service/stack.yaml in a checkout
mc2 ps
mc2 status

Save the token from the first normal mc2 server start (stderr on a TTY, else <data-dir>/bootstrap-token). Lost it? mc2 server token rotate --data-dir ~/.mc2.

Optional OTLP:

export MC2_OTLP_ENDPOINT=http://127.0.0.1:4317
# advanced collector example: examples/90-advanced/observability/collector-config.yaml

Guides: Quickstart · Stack YAML · Secrets · Testing

Binary modes

Command Role
mc2 server The orchestrator (SQLite, REST, scheduler, embedded msb runtime)
mc2 server token rotate [--data-dir d] Mint a new operator API token in place (the old one dies immediately)
mc2 server secrets purge [--data-dir d] --yes Delete stored secrets when secrets.key is lost, so the server can start again
mc2 node ls Show the local node (capacity, status)
mc2 up -f stack.yaml Bring up a stack (publish desired state, converge)
mc2 down <stack> [--volumes] Tear down a stack (instances + definition; volumes retained unless --volumes; rm is an alias)
mc2 config -f stack.yaml Validate and print a normalized stack config
mc2 ps [--stack s] [--service s] List instances / phases
mc2 exec <instance> <cmd…> Run a command inside a sandbox (argv verbatim; raw stdin forwarded)
mc2 logs <instance> [--tail N] [--follow] Sandbox logs; --follow streams
mc2 status Health + version + counts (mode/context-aware)
mc2 network [name | inst-ref] Network membership summary; <name> detail; <stack>/<service>/<ordinal> per-instance connectivity
mc2 ingress Desired ingress routes
mc2 volume ls Named volumes retained on the node (stack, size, path)
mc2 secret set|ls|rm Secrets (encrypted at rest; values never listed)
mc2 ssh add-key|keys|show-key|rm-key|ls|open|close SSH keys + open/close endpoints
mc2 setup First-run wizard: Server (flags + Traefik scaffold) or Client (save a context)
mc2 context set|use|ls Named API contexts (~/.mc2/config.toml, 0600)
mc2 doctor Host / msb readiness checks
mc2 completions <shell> Shell completions (bash/zsh/fish)

Server-backed listing commands accept -o json (mc2 context ls is table-only). Instance commands accept <stack>/<service>/<ordinal> in place of a UUID.

Local/remote client modes. The CLI resolves the URL and the token separately: --api > MC2_API > the context's URL > http://127.0.0.1:7443, and --token > MC2_API_KEY > the context's token, where the context is --context/MC2_CONTEXT or the current one in ~/.mc2/config.toml. A URL host outside loopback is remote mode (mc2 status reports it); plaintext http:// for a remote endpoint is refused unless you opt in with --allow-insecure-http / MC2_ALLOW_INSECURE_HTTP=1. Pair with the server's --public-hostname, which publishes the control plane itself through the Traefik ingress catalog so mc2 context set prod --api https://mc2.example.com --token mc2at_… && mc2 context use prod manages a remote install over TLS. See Manage a remote server.

Service networks: expose → default-allow east–west by name (<service>, <service>.<stack>.svc.mc2, or <service>.<network>.svc.mc2 across stacks on a shared named network); exposed ports are exclusive server-wide; mc2 network lists networks, members, and port owners. See examples/03-networks/.

Ingress: stack ingress: + ports: → the server writes a Traefik file-provider catalog (--ingress-config-dir). See examples/04-http-ingress/.

Examples: start with examples/01-hello-service/, work up through 07-startup-ordering; incomplete workflows are marked under examples/90-advanced/.

Repository layout

mc2/
  crates/           # Rust workspace (mc2 bin, server, api, store, runtime, metrics)
  site/             # Docs site (Next.js + MDX; `cd site && bun run dev`)
  examples/         # Compose-shaped stack YAML, ingress, OTLP samples
  justfile          # build, test, check, run-server

Docs site: a Next.js + MDX app lives in site/ — the public documentation (concepts, guides, references, recipes, security). Run cd site && bun install && bun run dev and open http://localhost:3000.

License

MIT — see LICENSE.

About

A Compose-shaped orchestrator for microsandbox microVMs.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages