A containerised Python execution engine. Send code over HTTP, get back streaming results — stdout, rich output, errors — exactly like executing cells in a Jupyter notebook.
Derived from e2b-dev/code-interpreter.
Two processes run inside the container:
- Jupyter Server (port 8888, internal) — manages Python kernel processes. Each kernel is a separate OS process with its own memory space.
- FastAPI server (port 49999, exposed) — the HTTP API. It holds a persistent WebSocket connection to each kernel and speaks the Jupyter wire protocol internally.
When you POST /execute, the server sends an execute_request over the kernel's WebSocket and streams back whatever the kernel emits — stdout, expression results, display data (images, HTML, etc.), errors — as newline-delimited JSON.
State is persistent across calls within a context. Variables, imports, and side effects from one execution are visible in the next, exactly like notebook cells.
Each context is a separate Python process. Kernels do not share memory or module state. They do share the container filesystem, network namespace, and system resources — there is no per-kernel resource quota. For user-level isolation, run one container per user.
Dockerfile # Image definition
start-up.sh # Entrypoint: starts Jupyter then the FastAPI server
jupyter-healthcheck.sh # Polls :8888/api/status before starting the API server
jupyter_server_config.py # Jupyter: allow_origin=*, no token, allow_root
ipython_kernel_config.py # IPython: unlimited sequence output
server/
main.py # FastAPI app, route handlers, lifespan
messaging.py # ContextWebSocket — WebSocket lifecycle, execution, streaming
contexts.py # create_context() — creates Jupyter session + WebSocket
consts.py # JUPYTER_BASE_URL
stream.py # StreamingListJsonResponse — newline-delimited JSON
errors.py # ExecutionError
api/models/ # Pydantic models for all request/response types
utils/locks.py # LockedMap — per-key async locks
tests/
test_api.py # Integration tests (requires container on :49999)
docker build -t code-interpreter .
docker run -p 49999:49999 code-interpreterAll endpoints are on port 49999. Execution results stream as newline-delimited JSON — one object per line, terminated by {"type": "end_of_execution"}.
Returns "OK" when the server is up.
Execute Python code. Streams results back as newline-delimited JSON.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | yes | Code to execute |
context_id |
string | no | Run in a specific context |
language |
string | no | Run in the default context for this language |
env_vars |
object | no | Environment variables to inject for this execution only |
context_id and language are mutually exclusive. Omit both to use the default Python context.
Example:
curl -X POST http://localhost:49999/execute \
-H "Content-Type: application/json" \
-d '{"code": "print(\"hello\")"}'Response stream:
Each line is one of:
Error responses:
| Status | Condition |
|---|---|
400 |
Both context_id and language provided |
404 |
context_id not found |
Create a new isolated Python kernel.
Request body:
| Field | Type | Default | Description |
|---|---|---|---|
language |
string | "python" |
Kernel language |
cwd |
string | "/home/user" |
Working directory |
Response:
{"id": "<uuid>", "language": "python", "cwd": "/home/user"}List all active contexts.
Response:
[
{"id": "<uuid>", "language": "python", "cwd": "/home/user"}
]Restart the kernel for a context. All state is wiped.
Shut down and remove a context.
All paths are workspace-relative POSIX paths rooted at /home/user. Path traversal outside the workspace is rejected with 400.
List directory contents. Defaults to the workspace root.
| Query param | Default | Description |
|---|---|---|
path |
. |
Workspace-relative directory path |
Response:
{
"path": ".",
"entries": [
{"name": "output.csv", "type": "file", "size": 1024, "modified": 1700000000.0},
{"name": "plots", "type": "dir", "size": null, "modified": 1700000000.0}
]
}Directories are listed before files.
Errors: 400 if path is a file, 404 if path does not exist.
Download a file. Returns raw bytes with a detected Content-Type header.
Errors: 404 if not found, 400 if path is a directory.
curl http://localhost:49999/files/output.csv
curl http://localhost:49999/files/plots/chart.png --output chart.pngCreate or overwrite a file. Send raw bytes as the request body. Parent directories are created automatically.
Response: 201 if created, 200 if overwritten.
curl -X PUT http://localhost:49999/files/input.txt \
-H "Content-Type: text/plain" \
--data-binary "hello world"
curl -X PUT http://localhost:49999/files/data/input.csv \
--data-binary @local_file.csvDelete a file or directory. Directory deletion is recursive.
Response: 204 on success, 404 if not found.
curl -X DELETE http://localhost:49999/files/output.csv
curl -X DELETE http://localhost:49999/files/plots # removes directory and contentsTests are integration tests — they require the container to be running on localhost:49999.
pip install pytest requests
pytest tests/ -vThe suite covers: health, stdout/stderr, expression results, rich output (matplotlib PNG), syntax and runtime errors, state persistence across calls, env var injection and cleanup, context creation/isolation/deletion/restart, file CRUD (create, download, list, delete, binary roundtrip, path traversal rejection), bidirectional kernel↔API file access, and error handling (404, 400).