Installation — Docker¶
This guide spins up BDPD inside a container, with no Node.js or Python toolchain on the host. It is an alternative to the from-source installation, not a replacement.
Use this path if you want a one-command setup, an isolated lab environment, or a reproducible base for CI / Colab-equivalent sandboxes.
Prerequisites¶
| Tool | Minimum Version | Notes |
|---|---|---|
| Docker Engine | ≥ 24 | Daemon and CLI |
| Docker Compose v2 | ≥ 2.20 | Invoked as docker compose (no hyphen) |
Check both with:
Distro note — Manjaro / Arch¶
On Manjaro and Arch the Compose v2 plugin ships in a separate package:
docker-compose here provides the v2 plugin (e.g. 5.1.3) used via
docker compose .... Most other distros (Ubuntu 22.04+, Debian 12+,
Fedora 38+) bundle Compose v2 with Docker Desktop or the
docker-compose-plugin apt package.
Distro note — Linux user group¶
Add your user to the docker group to skip sudo:
Step 1: Clone¶
Step 2: Build and Start¶
What this does:
- Builds image
bdpd:devfrom the repo rootDockerfile—node:20-bookworm-slimbase, Python 3.12 viauv, Node deps vianpm ci, Python deps viarequirements.txt. - Starts a single service
arena(container namebdpd-arena) listening onhttp://localhost:3000. - Bind-mounts
experiments/results,experiments/figures,data, andlogso sweeps run inside the container write to your host filesystem and survive container restarts.
Override the host port with BDPD_PORT:
Step 3: Wait for "healthy"¶
The image declares a HEALTHCHECK that probes /api/health every 30s
(with a 20s start-period for cold boot). Check the status:
Look for STATUS = Up X seconds (healthy). From a clean cold start
the transition is typically starting → healthy within ~10 seconds.
Confirm directly:
You should see {"ok": true, "ts": "..."}.
Step 4: Run something¶
Open http://localhost:3000/ in a browser for the static landing page,
or execute a sweep inside the container:
docker compose exec arena \
node experiments/experiment.js run aggressive_fraction \
--runs 1 --out /app/experiments/results
The resulting *.json and *.manifest.json files appear on your host
in experiments/results/ immediately, thanks to the bind mount.
Verify the Python environment:
docker compose exec arena python --version
docker compose exec arena python -c "import numpy, matplotlib, pandas"
Verify v1.0 / v1.1 features¶
The container ships with the full platform — World API, governance agents, Seneca engine, pollution links. A quick smoke test:
# Create a Seneca arena and run it to completion
curl -s -X POST http://localhost:3000/api/arenas \
-H "Content-Type: application/json" \
-d '{"model":{"engineMode":"seneca","commonsInitial":1.0,"maxTurns":10,"scheduler":"simultaneous","minPlayers":1}}' \
| jq '{arenaId, ok}'
You should see {"arenaId": "…", "ok": true}.
ARENA=$(curl -s -X POST http://localhost:3000/api/arenas \
-H "Content-Type: application/json" \
-d '{"model":{"commonsInitial":100,"commonsCapacity":100,"regenRate":0.1,"maxTurns":5,"scheduler":"simultaneous","minPlayers":1}}')
AID=$(echo "$ARENA" | jq -r .arenaId)
AK=$(echo "$ARENA" | jq -r .arenaKey)
OK=$(echo "$ARENA" | jq -r .ownerKey)
curl -s -X POST "http://localhost:3000/api/arenas/${AID}/join" \
-H "Content-Type: application/json" -H "X-Arena-Key: ${AK}" \
-d '{"agentType":"code","displayName":"vm-test","strategyCode":"function decide(obs, memory){return 1}"}'
curl -s -X POST "http://localhost:3000/api/arenas/${AID}/start" \
-H "X-Owner-Key: ${OK}"
curl -s -X POST "http://localhost:3000/api/arenas/${AID}/tick" \
-H "X-Owner-Key: ${OK}" | jq '.record.turn'
# Create a world with two linked arenas
W=$(curl -s -X POST http://localhost:3000/api/v1/worlds \
-H "Content-Type: application/json" -d '{"name":"docker-test"}')
WID=$(echo "$W" | jq -r .worldId)
WKEY=$(echo "$W" | jq -r .ownerKey)
echo "World: ${WID}"
A=$(curl -s -X POST http://localhost:3000/api/arenas \
-H "Content-Type: application/json" \
-d '{"model":{"commonsInitial":100,"commonsCapacity":100,"regenRate":0.1,"maxTurns":10}}')
AID=$(echo "$A" | jq -r .arenaId)
AOWN=$(echo "$A" | jq -r .ownerKey)
curl -s -X POST "http://localhost:3000/api/v1/worlds/${WID}/arenas/${AID}" \
-H "X-Owner-Key: ${WKEY}" -H "X-Arena-Owner-Key: ${AOWN}" | jq .ok
See World API for the full reference.
Step 5: Stop and clean up¶
docker compose down # stop and remove container; volumes preserved
docker compose down --rmi all # also remove the bdpd:dev image
Devcontainer (VS Code)¶
The repo ships a .devcontainer/devcontainer.json that reuses the
same Dockerfile. With VS Code + the Dev Containers extension:
- Open the cloned repo in VS Code.
- Run Dev Containers: Reopen in Container from the command palette.
- VS Code attaches to a container at
/app, with Python, Ruff, ESLint, and Quarto extensions pre-suggested, and port 3000 auto-forwarded (withopenBrowserOnce).
The postCreateCommand re-aligns Node and Python deps in case the
image was built statically.
What the image includes¶
The container ships the complete platform as of the current tag:
- Arena API (logistic and Seneca engines, all schedulers)
- Code agents (sandboxed VM) and HTTP agents
- Governance agents (sanctioner, treaty enforcer, pollution regulator)
- World API — polycentric federation with links, treaties, and meta-agents
- Experiment runner (
experiments/experiment.js) and Python analysis tools - Perturbation engine, observability transforms, plugin loader
No extra build steps or flags are needed to enable any of these.
What you do not get from this image¶
This is a lab / development image, not a hardened production deploy. Explicit non-goals:
- No multi-stage / slim production image (single-stage by design — readable over compact). If you push to a registry for deploy, rebuild multi-stage.
- No registry image — build locally; nothing is published to
registry.gitlab.comor Docker Hub yet. - No signed plugin support — native plugins under
plugins/execute with full container privileges; treat them as trusted code. - No GPU passthrough — local LLM inference inside the container is CPU-only
unless you wire
--gpusinto a custom compose override.
Troubleshooting¶
Cannot connect to the Docker daemon
The daemon is not running. On systemd distros:
permission denied while trying to connect to the Docker daemon socket
Your user is not in the docker group — see the Linux user group note
above.
Container starts but health stays starting forever
Check the logs:
A failed plugin load typically shows up as a stack trace right after the
Loading plugins... banner. The arena will not bind :3000 until all
plugins resolve.
address already in use on port 3000
Something else on the host is bound to 3000. Use a different host port:
Bind-mount permission errors on data/ or log/
The container runs as root by default; files it writes will be owned by
root on the host. If this is a problem, either change ownership after
the fact (sudo chown -R $USER:$USER data log experiments/results experiments/figures),
or add user: "${UID}:${GID}" to the compose service after exporting
UID / GID in your shell.
Rebuild after a requirements.txt or package.json change
The Dockerfile orders layers so package*.json and requirements.txt
are the cache-busting boundary — application source changes alone do not
re-trigger dep install.