Quickstart
Bring up the full stack locally and run your first sandboxed program.
This gets you a complete, working stack on one machine — API, worker, sandbox runner, file server, tool-call server, egress gateway, Redis, and MinIO — and then runs code in it.
Requirements
The default stack boots the sandbox inside a libkrun microVM and maps
/dev/kvm into the sandbox container. If your host has no usable /dev/kvm —
cheap VPSes, LXC containers, nested virtualisation without KVM passthrough —
Docker will fail to start that container. Jump to
running without KVM, or use the
--nokvm override shown below.
- Docker with Compose v2
/dev/kvmavailable (or use the no-KVM override)- Roughly 8 GB of free disk for the language runtimes on first start
Start the stack
docker compose up --buildOn first start the one-shot package_init service downloads the language
runtimes into ./data/pkgs, so nothing needs to be installed on the host. That
volume is reused on later starts and already-installed runtimes are skipped.
To install only a subset of languages — much faster, and usually what you want locally:
CODEAPI_LANGUAGES=python docker compose up --buildAccepted values are python, node, bun, bash, and java; the default is
all of them. Adding a language later (say CODEAPI_LANGUAGES=python,bun)
installs only the missing runtime into the existing volume. Set
FORCE_REBUILD=true to wipe and rebuild the volume from scratch.
The local Compose files run with LOCAL_MODE=true, which bypasses
authentication entirely and stamps every request with a fixed local
principal. That is fine on a loopback interface and nowhere else. See
Authentication before exposing this anywhere.
Run your first program
The API listens on port 3112 and serves everything under /v1.
curl -sX POST http://localhost:3112/v1/exec \
-H 'Content-Type: application/json' \
-d '{"lang":"py","code":"print(sum(range(10)))"}' | jq{
"session_id": "V1StGXR8Z5jdHi6BmyT",
"stdout": "45\n",
"stderr": "",
"files": [],
"code": 0,
"signal": null,
"wall_time": 412
}Non-zero exit codes are still HTTP 200
The API returns 200 whenever it successfully ran the program. If the user's
code crashed, that shows up as a non-zero code or a non-null signal — not
as an HTTP error. Check those fields, not just the status code.
Produce a file
Anything the program writes into its working directory comes back as a downloadable artifact:
curl -sX POST http://localhost:3112/v1/exec \
-H 'Content-Type: application/json' \
-d '{
"lang": "py",
"code": "import matplotlib.pyplot as plt\nplt.plot([1,4,9,16])\nplt.savefig(\"squares.png\")"
}' | jq '.files'[
{
"id": "8ZqL2mVxRt0PnCbWyKdSa",
"name": "squares.png",
"storage_session_id": "7fZq2NkP0aRcVbXsLmTdY",
"kind": "user"
}
]Download it with the session_id from the exec response and the file's id:
curl -s http://localhost:3112/v1/download/7fZq2NkP0aRcVbXsLmTdY/8ZqL2mVxRt0PnCbWyKdSa \
-o squares.pngSee Working with files for uploading inputs and the full file lifecycle.
Verify the stack is healthy
curl -s http://localhost:3112/v1/health # API
curl -s http://localhost:2000/api/v2/runtimes # installed runtimesThe runtimes call is the quickest way to confirm package_init finished and
the sandbox runner picked up the languages you asked for.
Using prebuilt images
To skip building from source, use the pull-based Compose file. It mirrors the
default stack but pulls every image from GHCR (ghcr.io/berry-13/codeapi-*),
published on each push to main and on version tags:
docker compose -f docker-compose.prebuilt.yml uppackage_init still populates ./data/pkgs on first start, so the same
CODEAPI_LANGUAGES and SANDBOX_PACKAGES_PATH options apply.
Prebuilt images are linux/amd64 only for now. On other architectures, build
from source with the default Compose file instead.