mm in a Docker Sandbox
Everything needed to run mm inside a Docker Sandbox: a template (the shell sandbox image with mm installed) and two kits that start it — one for the window mode in your terminal, one for an editor that speaks the Agent Client Protocol.
The full guide, with the reasoning, is in docs/en/how-to/run-in-a-docker-sandbox.md (français). This page is the short version.
docker-sbx/
├── publish.sh build + push the image, then publish the kits — the one command to release
├── build.sh builds the template image, k33g/mm-sandbox:<version> (PUSH=1: multi-arch, pushed)
├── sync-kits.sh points the kits at one image: owner + version
├── push-kits.sh publishes the kits to Docker Hub as OCI artifacts (sbx kit push)
├── template/
│ ├── Dockerfile docker/sandbox-templates:shell + mm (built from this checkout)
│ ├── mm-tui entry point of the mm-tui kit: `mm -tui "$@"`
│ └── mm-acp-info entry point of the mm-acp kit: prints the editor wiring, opens a shell
└── kits/
├── mm-tui/spec.yaml kind: sandbox — `sbx run` opens mm's window on the workspace
└── mm-acp/spec.yaml kind: sandbox — `sbx create` once, the editor runs `sbx exec -i … mm -acp`
0. Use the published kits (no build)
Every release publishes the image and the two kits to Docker Hub at mm's version:
| On Docker Hub | What it is |
|---|---|
k33g/mm-sandbox:0.1.6 |
The template image. You never name it yourself: each kit pulls it. |
k33g/mm-tui:0.1.6 |
The window-mode kit, for sbx run. |
k33g/mm-acp:0.1.6 |
The editor kit, for sbx create + sbx exec -i … mm -acp. |
Nothing to build, no --pull never, and no setting to change: Docker Hub is an allowed kit source out of the box (sbx settings get kit.allowedSources shows docker.io/), and a reference with a / but no registry host is read as docker.io/…. Only Docker Desktop with the sbx command, and a model server on your machine, are needed.
Look before you run. sbx kit inspect reads the kit from the Hub without creating anything: its image, its entry point, its arguments and their defaults, the ports it allows.
sbx kit inspect k33g/mm-tui:0.1.6
Window mode. From the project you want mm to work on:
cd /path/to/your/project
sbx run k33g/mm-tui:0.1.6 . -- agent.dmr.yaml
The first run pulls the kit and the image, creates the sandbox mm-tui-<directory>, mounts the current directory as its workspace and opens mm -tui agent.dmr.yaml in it. What follows -- is mm's own command line, so -- -config other.yaml works too. Without anything after --, mm reads the kit's config argument, then ./agent.yaml in the workspace:
sbx run k33g/mm-tui:0.1.6 . --kit-arg config=agent.dmr.yaml
Later, sbx run --name mm-tui-<directory> re-attaches to that sandbox; sbx ls lists them.
From an editor. Create the sandbox once, from the project the editor will open, with the configuration file baked in:
cd /path/to/your/project
sbx create --name mm-acp k33g/mm-acp:0.1.6 . --kit-arg config=agent.dmr.yaml
#sbx create k33g/mm-acp:0.1.6 . --name sbx-mm-acp
then 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"]
}
}
}
The editor starts mm -acp inside the sandbox for every conversation; sbx exec starts the sandbox first when it is stopped, and runs mm in the workspace with the baked-in AGENT_CONFIG. One sandbox per project, each with its own --name and its own editor entry. Section 3 has the details (another config for one session, where the logs are, why sbx exec -i and not sbx run).
Kit arguments. Both kits take the same two, passed with --kit-arg name=value (repeatable) to sbx run or sbx create:
--kit-arg |
Default | Effect |
|---|---|---|
config=<path> |
empty | The file mm reads (its AGENT_CONFIG): relative to the workspace, or absolute inside the sandbox. Empty: ./agent.yaml, then the built-in defaults. |
tag=<version> |
the kit's own version (0.1.6) |
Which k33g/mm-sandbox:<version> image to use. Normally left alone: a kit and its image share a version. |
Other versions. Replace 0.1.6 with the release you want, in the kit reference only — the kit brings the matching image. A kit can also be pinned by digest, k33g/mm-tui@sha256:…, the digest being what sbx kit inspect or the Hub page shows.
If sbx refuses the source. A message like its source is not in your allowlist means kit.allowedSources was narrowed on this machine; sbx settings set kit.allowedSources '["docker.io/"]' restores the default, or add docker.io/k33g/ to your list.
Sections 1–3 are the same flow with a template and kits built locally, for a work-in-progress mm.
1. Build the template
From the repository root:
./docker-sbx/build.sh # TAG from release.env: v0.1.6 → k33g/mm-sandbox:0.1.6
./docker-sbx/build.sh v0.1.5 # → k33g/mm-sandbox:0.1.5
The image tag is mm's tag without the v, the same rule as the release assets, and the script rewrites the kits' default tag argument to the version it built, so the kits always name the image just built. The image lands in the local Docker image store: every sbx run / sbx create below carries --pull never until you docker push k33g/mm-sandbox:0.1.6. If sbx runs on another machine, EXPORT=1 ./docker-sbx/build.sh also writes tmp/mm-sandbox-<version>.tar; load it 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
What follows -- reaches mm as its own arguments, so this runs mm -tui agent.dmr.yaml in the workspace. Any mm flag passes through the same way (-- -config other.yaml). Two kit arguments exist as well:
--kit-arg |
Default | Effect |
|---|---|---|
config=<path> |
empty | The file mm reads when nothing follows -- (its AGENT_CONFIG). Relative to the workspace, or absolute. Empty: ./agent.yaml, then the built-in defaults. |
tag=<version> |
the version last built (0.1.6) |
Which k33g/mm-sandbox:<version> image to use. |
sbx run --name <name> re-attaches to a sandbox that already exists. Sessions, when enabled in the YAML, are written under the workspace's .mm/sessions, which is your host directory.
3. From an editor (ACP) — kit mm-acp
Create the sandbox once, without attaching, from the project the editor will open. . is the workspace; sbx makes it absolute. A path that does not exist on your machine makes sbx ask "The selected workspace does not exist. Would you like to create it?" — answer N and fix the path. The kit path must be explicit: ./, ../ or absolute.
cd /path/to/your/project
sbx create --pull never --name mm-acp ./docker-sbx/kits/mm-acp . \
--kit-arg config=agent.dmr.yaml
# once the image is pushed (docker push k33g/mm-sandbox:0.1.6), --pull never is not needed:
sbx create --name mm-acp ./docker-sbx/kits/mm-acp . \
--kit-arg config=agent.dmr.yaml
Then register it in the editor. In Zed's settings.json:
{
"agent_servers": {
"📦 mini-me (dmr sbx)": {
"type": "custom",
"command": "sbx",
"args": ["exec", "-i", "mm-acp", "mm", "-acp"],
},
}
}
Or:
sbx create --pull never --name mm-acp-2 ./docker-sbx/kits/mm-acp .
And:
{
"agent_servers": {
"😄 mini-me (dmr sbx)": {
"type": "custom",
"command": "sbx",
"args": ["exec", "-i", "mm-acp-2", "mm", "-acp"],
"env": { "AGENT_CONFIG": "/Users/k33g/kDrive/Rickub/bots-garden/mini-me/demo/agent.dmr.yaml" }
},
}
}
The editor starts mm -acp inside the sandbox for every conversation; sbx exec starts the sandbox first when it is stopped, and runs mm in the workspace with the AGENT_CONFIG baked in by --kit-arg config. Everything the model does — commands, file edits — happens inside the sandbox, on the mounted project.
Why sbx exec -i and not sbx run: an ACP client speaks JSON-RPC on the agent's raw stdin/stdout, and sbx run always allocates a terminal. Attaching to this sandbox with sbx run --name mm-acp is still useful: its entry point prints the wiring above with the sandbox's real name, then opens a shell.
- One sandbox per project: the workspace is fixed at creation. Give each one its own
--nameand its own entry in the editor. - Another configuration file for one session:
"args": ["exec", "-i", "-e", "AGENT_CONFIG=agent.lsp.yaml", "mm-acp", "mm", "-acp"]. sbxnot found by the editor: use the absolute path printed bywhich sbxas"command".- Logs: in Zed,
dev: open acp logsshowsmm's banner, warnings and[acp] …trail.
4. Publishing a version
Publishing is done by hand, from a laptop with Docker Desktop (buildx and the sbx CLI). One command 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.1.6 → k33g/mm-sandbox:0.1.6, k33g/mm-tui:0.1.6, k33g/mm-acp:0.1.6
./docker-sbx/publish.sh v0.1.5 # another tag
With the two variables set, the script logs in with them and logs out at the end; with neither, it reuses the docker login you already have. It is two steps you can also run alone:
PUSH=1 ./docker-sbx/build.sh v0.2.3 # buildx multi-arch, pushed; proof: `imagetools inspect` lists both platforms
./docker-sbx/push-kits.sh v0.1.6 # sbx kit validate + sbx kit push, for each kit
Both call sync-kits.sh first, so the published kits always name the image just pushed, and the kits' spec.yaml end up saying the new version — commit them. sbx kit push talks to the registry directly and authenticates from the Docker credential store; no sandbox daemon is involved. IMAGE_OWNER=<handle> publishes under another account. Nothing in CI publishes to the Hub: the Release workflow only builds the binaries.
The model server stays on the host
Inside a sandbox localhost is the sandbox itself. Docker Model Runner and llama-server keep running on your machine and are reached through host.docker.internal, which the sandbox proxy maps to your host's localhost:
agent.dmr.yamlalready hasfallback: http://host.docker.internal:12434/engines/v1— nothing to change.agent.llamacpp.yamlhas no fallback: pass--env AGENT_BASE_URL=http://host.docker.internal:8080/v1tosbx run/sbx create, or edit itsbaseUrl.
Both kits allow ports 12434 (Docker Model Runner) and 8080 (llama-server) on the host. Another port: sbx policy allow network host.docker.internal:<port>.
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 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
|