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.
| 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.
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,ingressin a git-committable file. Unknown keys are rejected, not ignored. - Desired state, converged.
mc2 upreconciles replicas, restart policies, and health-gated startup ordering (depends_on: {db: {condition: service_healthy}});mc2 down(aliasrm, 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 nextup. 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 execandmc2 logs --followare 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 toallowHosts. - 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 networkshows it all. - Per-replica ports, no bookkeeping.
scale: 3turns8080into8080, 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).
- Launch a swarm of agents.
scale: 20on an agent image puts each agent in its own hardware-isolated microVM;restartkeeps them alive through crashes, andmc2 exec/mc2 logs --followdrop 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 sameservices,ports,depends_on, andenvironmentshape now boots each service as a hardware-isolated microVM, with no image builds or platform teams. - Remote coding agents over SSH.
ssh: trueon a dev VM plus a key frommc2 ssh add-keygives 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> --volumesand 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, ornone).
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 --versionVERSION 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.shRequirements: 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 setupmc2 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.
- 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. - 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-authruns this start open; loopback only unless you add--allow-unauthenticated-remote.) - On your laptop (or the same machine) run
mc2 setupagain and choose Client. Paste the control-plane URL and token (leave the token blank for a--no-authserver); it saves a named context in~/.mc2/config.toml(0600) and can verify the connection. - 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.
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 statusSave 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.yamlGuides: Quickstart · Stack YAML · Secrets · Testing
| 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/.
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.
MIT — see LICENSE.