Replace QEMU emulation in the release workflow with a native-runner matrix (ubuntu-latest / ubuntu-24.04-arm): each platform builds and pushes by digest via PUSH_BY_DIGEST, and a merge job assembles the multi-arch manifest lists with imagetools. Add `bake local` targets that build only the host's native platform and load into the local image store, plus a dhi.io registry login for the hardened bases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
code-interpreter-go
Go port of the code-interpreter FastAPI service. Manages execution of
arbitrary Python code; the server itself never runs user-defined Python — it
delegates to an isolated executor backend (Docker container or Kubernetes
pod).
Layout
cmd/code-interpreter-api/ entrypoint (port of app/main.py)
internal/api/ HTTP routes + schemas (app/api/routes.py, app/models/schemas.py)
internal/bootstrap/ Docker environment setup at startup (port of entrypoint.sh)
internal/executor/ backend abstraction, Docker and Kubernetes executors
internal/storage/ uploaded-file storage (app/services/file_storage.py)
internal/config/ environment-based settings (app/app_configs.py)
internal/logging/ plain/JSON logging (app/logging_config.py)
internal/imageref/ Docker image reference normalization (app/image_ref.py)
Build & run
go build ./...
go run ./cmd/code-interpreter-api # respects HOST/PORT, defaults 0.0.0.0:8000
Docker image (same Docker-in-Docker / Docker-out-of-Docker support as the
Python image; the entrypoint.sh logic is built into the binary). Bases
default to Docker Hardened Images mirrored into the org namespace
(onyxdotapp/dhi-golang, onyxdotapp/dhi-debian); override the build args
to use public images:
Image variants are defined in docker-bake.hcl; CI builds each platform on
a native runner from the same definition and merges the manifests.
Local builds use the local targets, which build only your machine's native
platform (no QEMU needed) and load the result into docker images:
docker buildx bake local # code-interpreter-go:local and :local-slim
docker buildx bake slim-local # just the slim variant
# Without a DHI subscription:
BUILD_IMAGE=golang:1.26-bookworm RUNTIME_IMAGE=debian:bookworm-slim \
docker buildx bake local
# Plain docker build works too:
docker build . -t code-interpreter-go
Published images
Each release publishes two variants of onyxdotapp/code-interpreter-go:
:<version>(default) — includes the Docker daemon, so every deployment mode works out of the box, including Docker-in-Docker. Pick this if unsure.:<version>-slim— no docker packages at all (built withSKIP_NESTED_DOCKER=1). Opt in when deploying with a mounted socket (Docker-out-of-Docker) or on Kubernetes; because the service talks to the Engine API socket directly, DooD needs no docker packages in the image — the Python service still required the docker CLI for that.
Deployment modes
In order of preference:
- Kubernetes (
EXECUTOR_BACKEND=kubernetes): executors run as locked down pods; no Docker anywhere. Use the-slimimage. - Docker-out-of-Docker: mount a Docker socket
(
-v /var/run/docker.sock:/var/run/docker.sock); executor containers are created on that daemon. Use the-slimimage. Prefer mounting a rootless daemon's socket ($XDG_RUNTIME_DIR/docker.sock): a rootful socket is root-equivalent on the host, while a rootless daemon caps the blast radius of a service compromise at one unprivileged user and adds user-namespace isolation around executors. Rootless requires cgroup v2 with systemd delegation for the memory/pids limits to apply. - Docker-in-Docker (default image,
--privileged, no socket mounted): the service starts its own nested daemon. For environments where no daemon can be shared. Note--privilegeddisables seccomp/AppArmor and is effectively root on the host — rootless DooD is the safer choice when available.
Test
go vet ./...
go test ./...
# Optional end-to-end test against a real Docker daemon:
CODE_INTERPRETER_GO_E2E=1 go test ./internal/executor -run TestDockerExecutorEndToEnd
Configuration
Same environment variables as the Python service: EXECUTOR_BACKEND
(docker|kubernetes), PYTHON_EXECUTOR_DOCKER_IMAGE/RUN_ARGS/NETWORK,
KUBERNETES_EXECUTOR_NAMESPACE/IMAGE/SERVICE_ACCOUNT/NET_ADMIN_LOCKDOWN,
MAX_EXEC_TIMEOUT_MS, MAX_OUTPUT_BYTES, CPU_TIME_LIMIT_SEC,
MEMORY_LIMIT_MB, HOST, PORT, LOG_LEVEL, LOG_FORMAT,
FILE_STORAGE_DIR, MAX_FILE_SIZE_MB, FILE_TTL_SEC.
Docker-specific changes:
- The service talks to the Docker Engine API directly (no docker CLI
subprocesses), so the daemon connection uses the standard
DOCKER_HOST/DOCKER_API_VERSION/DOCKER_CERT_PATH/DOCKER_TLS_VERIFYvariables, defaulting to the local socket.PYTHON_EXECUTOR_DOCKER_BINis no longer read. PYTHON_EXECUTOR_DOCKER_RUN_ARGSaccepts a documented subset instead of arbitrarydocker runflags:--label,--env/-e,--add-host,--dns, and--ulimit(space or=separated). Any other flag fails at startup rather than being silently dropped.
API
Same surface and payloads as the Python service:
GET /healthPOST /v1/executeandPOST /v1/execute/stream(SSE:output,result,errorevents)POST/GET/DELETE /v1/files,GET /v1/files/{file_id}POST /v1/sessions,DELETE /v1/sessions/{session_id},POST /v1/sessions/{session_id}/bash
Intentional differences from the Python implementation
- No shell entrypoint: the binary itself performs entrypoint.sh's job at
startup — mode detection (Kubernetes / external
DOCKER_HOST/ mounted socket / Docker-in-Docker), cgroup v2 nesting setup, launching and supervising a nesteddockerd, and waiting for readiness via an Engine API ping instead ofdocker info. The image runs the binary directly and needs no shell at runtime. - Docker Engine API instead of the docker CLI: containers are managed via
github.com/docker/docker/client(create/start/kill/list/remove, exec attach with demultiplexed streams, image pull), giving typed error handling (e.g. not-found) instead of parsing subprocess stderr. File staging and the workspace snapshot still runtarinside the container over the exec API, because/workspaceis a tmpfs mount that the engine's copy API cannot see. See the configuration notes above for thePYTHON_EXECUTOR_DOCKER_BINandRUN_ARGSimplications. - Validation error bodies: request-validation failures return
{"detail": "<message>"}(a string) with HTTP 422, rather than Pydantic's array-of-objectsdetail. Status codes are unchanged. - Ephemeral container keepalive: the idle
sleepfor one-shot executions istimeout_ms/1000 + 10seconds. The Python version computedtimeout_ms * 1000 + 10(a unit slip yielding multi-day sleeps); the container is killed after execution either way, but a crashed API now leaks the container for seconds instead of days. - Kubernetes workspace snapshot: streamed over SPDY, which is
binary-safe, so the
tar | base64round-trip the websocket client needed is gone — the pod-side command is plaintar -c. - Hardened base images: the Dockerfile defaults to Docker Hardened Images
(DHI)
-devvariants for both build and runtime stages, overridable viaBUILD_IMAGE/RUNTIME_IMAGEbuild args. - No OpenAPI docs endpoints:
/docs,/redoc, and/openapi.jsonare not served.