Local Docker mesh¶
Reproducible 4-node OpenClaw + ai-memory federation mesh on a single workstation. Bypasses DigitalOcean entirely; every run is peer-reviewable, re-testable, and reproducible from the scripts in this repository.
Status (2026-04-24): first-class cell alongside the DO IronClaw and
Hermes matrix. Certification criterion — three consecutive full-testbook
overall_pass=true runs — is satisfied for openclaw via
runs/a2a-openclaw-v0.6.2-local-docker-{r1,r2,r3}/ on release/v0.6.2 @ 3e018d6.
Why this exists¶
OpenClaw's install-time memory demand (>8 GB) historically forced the harness onto DO General Purpose tier droplets, which require a paid account-tier upgrade. The local Docker mesh removes that barrier:
- Each openclaw container gets 16 GB of memory (
mem_limit: 16gin compose) - The memory-only aggregator gets 4 GB
- Total budget 52 GB — fits on a workstation with ~64 GB+ of RAM
- Provisioning wall-clock: ~15 sec vs ~5-8 min for the DO Terraform flow
- Exactly the same scenario harness +
ai-memoryrelease binary + testbook
Host prerequisites¶
| Requirement | Minimum | Verified on |
|---|---|---|
| Linux (any distro with Docker) | 4.x kernel, user namespaces | Pop!_OS 24.04 LTS |
| RAM | 64 GB (3×16 GB openclaw + 4 GB aggregator + OS headroom) | 93 GB host |
| CPU | 8 cores | 14 cores |
| Disk | 25 GB free (images: 10 GB, databases + logs: ~1 GB / round) | 596 GB free |
| Docker Engine | 20.10+ (29.x tested) | 29.1.3 |
| Docker Compose v2 | 2.20+ | 2.40.3 |
nft / nftables |
any recent | 1.1.2 |
| xAI API key | required for openclaw → Grok | — |
Install Docker on Ubuntu-derived hosts:
apt install docker.io docker-compose-v2
Build¶
cd docker/
# Stage the ai-memory release binary (from the ai-memory-mcp repo at
# release/v0.6.2 @ 3e018d6 or later):
mkdir -p bin
cp /path/to/ai-memory-mcp/target/release/ai-memory bin/
# Build the base image (ubuntu:24.04 + ai-memory + MiniLM pre-baked):
docker build --network host -t ai-memory-base:local -f Dockerfile.base .
# Build the openclaw image (base + openclaw install.sh + NodeSource Node.js v22):
docker build --network host -t ai-memory-openclaw:local \
--build-arg AI_MEMORY_BASE=ai-memory-base:local \
-f Dockerfile.openclaw .
Why
--network hostat build time? Docker's default bridge MASQUERADE is silently dropped by hosts running Tailscale + a restrictiveinet filter forwardchain (e.g. the CCC Gateway topology).--network hostuses the host netns directly for the build-timeapt-get update+curl | bash -s openclaw install. Not needed at runtime oncehost-nft-docker-forward.shis applied (see next section).
Host firewall workaround (CCC Gateway / Tailscale hosts only)¶
Hosts that run Tailscale AND a custom inet filter forward policy drop
ruleset (for example a Family Safety / CCC Gateway configuration) drop
Docker user-defined bridge egress before it reaches the external NIC.
Symptom: curl from a container hangs on connect; tcpdump shows SYN
on the bridge but zero bytes on the external interface.
# Idempotent. Adds four nft rules allowing Docker subnets to forward.
# NOT persistent across reboot; re-run after boot.
sudo bash docker/host-nft-docker-forward.sh
Rules added:
ip saddr 10.88.0.0/16 accept # docker-compose user-defined bridge
ip saddr 172.17.0.0/16 accept # docker0 default bridge
ip daddr 10.88.0.0/16 ct state new accept
ip daddr 172.17.0.0/16 ct state new accept
Bring up the mesh¶
export XAI_API_KEY=sk-... # or source /path/to/.env
docker compose -f docker-compose.openclaw.yml up -d
# Wait for 4/4 healthy:
docker compose -f docker-compose.openclaw.yml ps
Expected layout:
| Container | Role | agent_id | Bridge IP | Memory |
|---|---|---|---|---|
a2a-node-1 |
agent | ai:alice |
10.88.1.11 | 16 GB |
a2a-node-2 |
agent | ai:bob |
10.88.1.12 | 16 GB |
a2a-node-3 |
agent | ai:charlie |
10.88.1.13 | 16 GB |
a2a-node-4 |
memory-only aggregator | — | 10.88.1.14 | 4 GB |
Smoke test¶
bash docker/smoke.sh
Expected output ends with [smoke …] SMOKE PASS. The smoke script
verifies cross-node write-read on 3 namespace pairs and a 500-row bulk
fanout (S40-style).
Run the full testbook¶
cd docker/
bash run-testbook.sh a2a-openclaw-v0.6.2-local-docker-r1
Round produces runs/a2a-openclaw-v0.6.2-local-docker-r1/ with:
scenario-<id>.json— one per scenario, raw JSON reportscenario-<id>.log— stderr trace, human-readablea2a-summary.json— aggregate (same shape as DO runs)campaign.meta.json— infra + provenanceindex.html— auto-generated Pages evidence
To satisfy the 3-of-3 certification bar:
bash run-testbook.sh a2a-openclaw-v0.6.2-local-docker-r1
bash run-testbook.sh a2a-openclaw-v0.6.2-local-docker-r2
bash run-testbook.sh a2a-openclaw-v0.6.2-local-docker-r3
DO NOT tear the mesh down between rounds — the compose stack stays
up; runs just replay against the live mesh. Per-round artifacts
remain independent under runs/.
What gets run¶
Same testbook v3.0.0 always-on set as the DO workflow:
1 1b 2 4 5 6 9 10 11 12 13 14 15 16 17 18 22 23 24 25 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
35 scenarios. tls/mtls modes are deferred to a later iteration
(entrypoint.sh rejects non-off until the ephemeral CA + cert
volume-mount design lands).
Provenance in each run¶
Every a2a-summary.json committed under runs/ captures:
{
"campaign_id": "a2a-openclaw-v0.6.2-local-docker-rN",
"ai_memory_git_ref": "release/v0.6.2",
"meta": {
"topology": "local-docker",
"infra": {
"provider": "local-docker",
"host": "<hostname>",
"mesh_topology": "4-node bridge (3 openclaw agents + 1 memory-only aggregator)",
"nodes": [...]
},
"timing": { "start": "...", "end": "..." },
"ci": { "runner": "local-docker", "operator": "..." }
}
}
Peer reviewers can reconstruct the entire run from:
- This document + the host pre-req list above
docker/Dockerfile.base+docker/Dockerfile.openclaw(image recipe)docker/entrypoint.sh(container startup contract)docker/docker-compose.openclaw.yml(topology + memory limits)docker/run-testbook.sh(runner + aggregator)scripts/a2a_harness.pywithTOPOLOGY=local-docker(harness)scripts/scenarios/*.py(scenarios — identical to DO)release/v0.6.2 @ 3e018d6for theai-memorybinary
Tear down¶
docker compose -f docker-compose.openclaw.yml down -v # drops volumes
docker image prune -f # optional — free disk
Note the nft rules installed by host-nft-docker-forward.sh persist
until the next reboot or explicit nft delete rule — by design so
back-to-back test rounds don't re-prompt.
Known gaps (tracked)¶
- TLS / mTLS not yet supported in local-docker topology (issue #54)
a2a-gate.ymlworkflow doesn't dispatch local-docker runs — they are local-only until a self-hosted runner with 64 GB+ RAM is in scope- Scenario parallelism is sequential (matches DO) — a parallel mode is feasible but would be a separate validation exercise