How to run mini-me in a Docker Sandbox
This guide shows how to run mm inside a Docker Sandbox, so that every command the model runs and every file it edits happens in a container, on a mounted copy of your project. It covers the two ways of using it: the window mode in your terminal, and from an editor through the Agent Client Protocol. It assumes Docker Desktop with the sbx command, and a model server (Docker Model Runner or llama-server) running on your machine.
These are the v2 kits (
spec.yaml). The same two kits also exist for the kit specification v3, underdocker-sbx-v3/: see Run mini-me with the v3 sandbox kits.
Everything lives under docker-sbx/ in the repository:
| Piece | What it is |
|---|---|
template/Dockerfile |
The template: Docker Sandboxes' shell image with mm built from this checkout, plus the two entry points below. |
kits/mm-tui/spec.yaml |
A sandbox kit: sbx run on it opens mm's window mode on the workspace. |
kits/mm-acp/spec.yaml |
A sandbox kit for an editor: created once with sbx create, the editor then runs mm -acp inside it. |
build.sh |
Builds the template image, k33g/mm-sandbox:<version>; PUSH=1 builds it for linux/amd64 and linux/arm64 and pushes it. |
sync-kits.sh, push-kits.sh |
Point the kits at one image, and publish them to Docker Hub as OCI kit artifacts. |
Use the published kits
Every release publishes the image and both kits to Docker Hub at mm's version — k33g/mm-sandbox:0.2.1, k33g/mm-tui:0.2.1, k33g/mm-acp:0.2.1 — so the shortest path needs no build at all. Docker Hub is an allowed kit source by default (sbx settings get kit.allowedSources shows docker.io/), and a reference with a / but no registry host is read as docker.io/…:
cd /path/to/your/project
sbx run k33g/mm-tui:0.2.1 . -- agent.dmr.yaml
for the window mode, and for an editor:
sbx create --name mm-acp k33g/mm-acp:0.2.1 . --kit-arg config=agent.dmr.yaml
then the editor registration of step 3 below. No --pull never: the kit names k33g/mm-sandbox:0.2.1 and sbx pulls it. sbx kit inspect k33g/mm-tui:0.2.1 shows what a kit declares before you run it. The steps that follow build the same thing locally, for a work-in-progress mm.
Steps
1. Build the template
From the repository root:
./docker-sbx/build.sh
This reads mm's tag from release.env (v0.2.1) and builds k33g/mm-sandbox:0.2.1: the image tag is the version without the v, like the release assets, and mm inside is stamped with the tag — mm --version is run in the image as proof. The script then rewrites the kits' default tag argument to 0.2.1, so the kits and the image move together. ./docker-sbx/build.sh v0.1.5 builds another version; an earlier image stays reachable with --kit-arg tag=0.1.4.
The image is in your local Docker image store: every sbx run or sbx create on these kits takes --pull never until the image is pushed (docker push k33g/mm-sandbox:0.2.1), or sbx tries to pull it and fails. If sbx runs on another machine, EXPORT=1 ./docker-sbx/build.sh also writes tmp/mm-sandbox-0.2.1.tar, to load there with sbx template load.
2. Window mode in the terminal (kit mm-tui)
From the project you want mm to work on:
sbx run --pull never ./docker-sbx/kits/mm-tui . -- agent.dmr.yaml
The first time, sbx creates the sandbox, mounts the current directory as its workspace and attaches; then it runs the kit's entry point with what follows -- as arguments — here mm -tui agent.dmr.yaml, in the workspace. Any mm flag passes the same way: -- -config other.yaml.
Without anything after --, mm reads the kit's config argument, then ./agent.yaml in the workspace, then its built-in defaults:
sbx run --pull never ./docker-sbx/kits/mm-tui . --kit-arg config=agent.dmr.yaml
--kit-arg |
Default | Effect |
|---|---|---|
config=<path> |
empty | The configuration file, as mm's AGENT_CONFIG: relative to the workspace, or absolute inside the sandbox. |
tag=<version> |
the version last built (0.2.1) |
Which k33g/mm-sandbox:<version> image the sandbox is made from. |
The sandbox keeps its name (mm-tui-<directory> unless you pass --name); sbx run --name <name> re-attaches to it later. Inside, the window is the one described in Use the window mode. Sessions, when sessions.enabled is on, are written under the workspace's .mm/sessions — that is your host directory, so they survive the sandbox.
3. From an editor (kit mm-acp)
An editor talks to mm -acp over the agent's raw stdin and stdout. sbx run cannot carry that: it always allocates a terminal. So the sandbox is created once without attaching, and the editor starts mm inside it with sbx exec -i, which pipes stdin and stdout untouched.
-
From the project the editor will open, create the sandbox with the configuration file baked in (
.is the workspace, sbx makes it absolute; the kit path must be an explicit./,../or absolute path):cd /path/to/your/project sbx create --pull never --name mm-acp /path/to/mini-me/docker-sbx/kits/mm-acp . \ --kit-arg config=agent.dmr.yamlA workspace path that does not exist on your machine makes sbx ask "The selected workspace does not exist. Would you like to create it?" — answer
Nand fix the path.--kit-arg configbecomes the container'sAGENT_CONFIG, relative to the workspace because that is wheresbx execstartsmm. The workspace is fixed at creation: one sandbox per project, each with its own--name. -
Register it in the editor. In Zed's
settings.json:{ "agent_servers": { "mini-me (sandbox)": { "type": "custom", "command": "sbx", "args": ["exec", "-i", "mm-acp", "mm", "-acp"] } } }Compared with the host-side setup, the command is
sbxand noenvis needed:AGENT_CONFIGis already in the container. If the editor cannot findsbx, put the absolute path fromwhich sbxin"command". -
Open the agent panel and pick mini-me (sandbox). The editor starts
mm -acpin the sandbox for every conversation;sbx execstarts the sandbox first when it is stopped. Tool calls, permission dialogs,@filementions and the slash-command menu work as they do on the host — the difference is where the commands run.
Attaching to this sandbox with sbx run --name mm-acp does not start mm: its entry point prints the wiring above with the sandbox's real name and paths, then opens a shell inside the sandbox. sbx exec -it mm-acp bash is the shorter way to that shell.
The model server stays on the host
Inside a sandbox localhost is the sandbox itself. Your model server keeps running on your machine and is reached through host.docker.internal, which the sandbox proxy maps to your host's localhost.
- Docker Model Runner — nothing to change:
agent.dmr.yamlalready hasfallback: http://host.docker.internal:12434/engines/v1, andmmswitches to it whenlocalhost:12434does not answer (the banner says which URL won). - llama-server —
agent.llamacpp.yamlhas no fallback. Either edit itsbaseUrltohttp://host.docker.internal:8080/v1, or pass--env AGENT_BASE_URL=http://host.docker.internal:8080/v1tosbx run/sbx create(see Run with llama.cpp).
Both kits allow ports 12434 and 8080 on the host in the sandbox's network policy. For another port: sbx policy allow network host.docker.internal:<port>.
Publishing a version
Publishing is a manual step, from a machine with Docker Desktop (buildx and the sbx CLI); the Release workflow builds only the binaries. One script builds the image for linux/amd64 and linux/arm64, pushes it, then validates and pushes the two kits, all at mm's tag:
export DOCKER_HANDLE=k33g # the Docker Hub account, also the image owner
export DOCKER_PAT=dckr_pat_… # an access token with read/write scope
./docker-sbx/publish.sh # TAG from release.env: v0.2.1 → k33g/mm-sandbox:0.2.1, k33g/mm-tui:0.2.1, k33g/mm-acp:0.2.1
With both variables set, the script logs in with them and logs out when it is done; with neither, it reuses your existing docker login. Its two halves run alone as PUSH=1 ./docker-sbx/build.sh v0.2.1 (image) and ./docker-sbx/push-kits.sh v0.2.1 (kits, SBX=/path/to/sbx if the CLI is not on the PATH). Both call sync-kits.sh first, which rewrites the kits' version, default tag and image owner, so a published kit always names the image pushed with it — commit the two spec.yaml afterwards. sbx kit push talks to the registry directly and authenticates from the Docker credential store, no sandbox daemon involved. IMAGE_OWNER=<handle> publishes under another account.
MCP servers through the sbx gateway (GitHub as the example)
Inside a sandbox, mm does not talk to MCP servers itself and holds no token. sbx starts one MCP gateway per sandbox, on the host. When it creates the sandbox, it bakes two variables into the container: MCP_GATEWAY_URL (http://mcp-gateway.docker.internal/mcp) and MCP_SENTINEL_TOKEN_NAME, a placeholder that the sandbox proxy swaps for the real credential. When mm finds MCP_GATEWAY_URL and mcp.enabled: true, it adds a server called sbx-gateway, and its tools show up as sbx-gateway_<tool>. The kit needs no network rule for it, because the proxy routes that host itself.
-
Register GitHub's official server once, on the host, through
docker-sbx/mcp/github-mcp.sh. The script startsghcr.io/github/github-mcp-serverin Docker with your token. sbx runs it as a child of sandboxd, which does not see your shell's variables. So store the token once in the macOS keychain under the servicegithub-mcp. The script also acceptsGITHUB_PERSONAL_ACCESS_TOKEN,GITHUB_OAUTH_TOKENor a logged-ingh. sbx runs it on the host, so the token never enters the sandbox. By default it exposes a read-only set of 25 tools, includingget_meandget_teams. SetGITHUB_READ_ONLY=0and a widerGITHUB_TOOLSETSin the script's environment for more.security add-generic-password -U -a "$USER" -s github-mcp -w # prompts for the token sbx mcp rm github 2>/dev/null # an earlier "github" is the ARCHIVED catalog server sbx mcp add github-mm --command "$PWD/docker-sbx/mcp/github-mcp.sh" sbx mcp ls # github-mm must be readyDo not use the name
githubalone. The Docker MCP catalog resolves it to "GitHub (Archived)", the old npm server, which has noget_meand returns other tools. The catalog'sgithub-officialdoes not help in sbx's local mode either. There, a catalog image is started as a baredocker runwith no environment, so it would get no token. -
Create a new sandbox with that server in its fixed set.
--static-mcpis ansbxflag: it goes before--, since everything after--goes tomm. It only applies at creation, so remove an older sandbox of the same name first:cd /path/to/project sbx rm mm-mcp-github 2>/dev/null sbx run --name mm-mcp-github --static-mcp github-mm k33g/mm-tui:<version> . -- agent.dmr.sbx.yaml -
The configuration only needs MCP turned on.
demo/agent.dmr.sbx.yamlhas:mcp: enabled: true permissions: ask discoveryTimeout: 10s servers: [] # the gateway is added by mm when MCP_GATEWAY_URL is setThe banner then lists
sbx-gateway_get_me,sbx-gateway_list_issues, and so on intools:.
A kit image built before this change cannot use the gateway. It discovers nothing and warns method "tools/list" is invalid during session initialization. The gateway follows MCP's Streamable HTTP transport strictly: it needs initialize first, then the Mcp-Session-Id it returns on every request. Older mm skipped both. That was harmless against api.githubcopilot.com but fatal here. Rebuild and publish the kit after upgrading.
If the banner lists no sbx-gateway_* tools, check the registration with sbx mcp inspect github-mm, then read sbx's MCP log, mcp/mcp.log under its state directory. github-mcp: no token there means the script found no token: check the keychain item with security find-generic-password -s github-mcp. The remote endpoint https://api.githubcopilot.com/mcp/ is another route, but only with a GitHub OAuth App. GitHub offers no dynamic client registration, so in local mode sbx mcp add refuses it without --client-id, even with an Authorization header.
Name the repository in your requests, for example "list the open issues of k33g/my-repo". A small model given only "my organization" invents an owner and a repository, and every call comes back Not Found.
Variants
- Another configuration for one editor session. Add
"-e", "AGENT_CONFIG=agent.lsp.yaml"before the sandbox name in"args";sbx exec -eoverrides the baked-in value for that run only. - Skills.
skillsDiris relative to the directorymmruns in, which is the workspace in both kits — the same rule as on the host. - Docker inside the sandbox. Build the template on the Docker-in-Docker shell instead:
SHELL_TEMPLATE=docker/sandbox-templates:shell-docker ./docker-sbx/build.sh. - Read the logs. In the window mode they are on screen; over ACP they go to stderr, which Zed shows under
dev: open acp logs. - Remove a sandbox.
sbx rm mm-acp. The project directory is a bind mount and is not touched.
See also
- The kit format these two files follow: the Docker Sandboxes kit specification (
schemaVersion: "2"), bundled with thesbxsources. - The same kits for the kit specification v3: Run mini-me with the v3 sandbox kits.
- What
mm -acpexposes to the editor: ACP reference. - The flags and environment variables
mmreads: command line reference.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
|