aivyx-coder · ManualContents ↓

Docker

A secondary, optional way to run aivyx-coder — the native x86_64-linux-musl/darwin-aarch64 binaries (see Install and first run) stay the primary, recommended distribution.

Build and run

Each release is also published as ghcr.io/aivyx-agent/aivyx-coder (:latest or a version tag) — use that in place of aivyx-coder below to skip the build. Or build it yourself:

docker build -t aivyx-coder .
mkdir -p ~/.config/aivyx-coder ~/.local/state/aivyx-coder
docker run -it --rm \
  --user "$(id -u):$(id -g)" \
  --add-host=host.docker.internal:host-gateway \
  -v "$(pwd)":/workspace -w /workspace \
  -v "$HOME/.config/aivyx-coder":/home/coder/.config/aivyx-coder \
  -v "$HOME/.local/state/aivyx-coder":/home/coder/.local/state/aivyx-coder \
  aivyx-coder

The three -v flags bind-mount the project directory being worked on, plus aivyx-coder’s own config and session-state directories, so config.toml and session history survive across separate docker run invocations rather than being lost every time the container exits (each run is otherwise a fresh, ephemeral container). Create the two directories first, as above — otherwise Docker creates them owned by root.

--user runs it as you, so the files it writes are yours, and git accepts the project. As root, git refuses a mounted repository owned by someone else (“detected dubious ownership”): checkpoints and /undo stop working, and --auto refuses to start because it finds no git worktree. The image’s home, /home/coder, is writable by any user. If the LLM backend (Ollama, llama-server, etc.) runs on the host rather than inside another container, set base_url in config.toml to http://host.docker.internal:<port>/v1 — the --add-host flag above is required for that hostname to resolve on Linux (confirmed: it does not resolve by default there, unlike Docker Desktop on Mac/Windows).

Toolchains

The image holds aivyx-coder, git, bash and coreutils on Alpine — no compilers, language runtimes or package managers. A command that needs one (cargo test, npm test, pytest) fails inside the container with “not found”, so /test, verification and --auto can’t check anything. For such a project, build your own image on top with the toolchain added, for example for Rust:

FROM ghcr.io/aivyx-agent/aivyx-coder:latest
RUN apk add --no-cache cargo rust rust-clippy musl-dev

The sandbox lets commands read the system directories Alpine installs into (/usr, /lib) and ~/.cargo, so a toolchain added this way works confined. Dependencies are downloaded into the container’s home (/home/coder), so mount a directory there, or give the image a fetched cache, to avoid downloading them on every run. Some test suites also expect tools or settings a minimal image lacks (an editor, a $USER variable); those tests fail in the container whatever the code does.

The sandbox in a container

The real OS-level sandbox (Linux Landlock + seccomp) is on by default inside the container exactly as on a bare host — confirmed directly (not assumed) via aivyx-confine’s own test suite passing inside a real container built from this same Dockerfile, and via a standalone probe confirming a Landlock grant scoped to a bind-mounted directory correctly allows reads/writes within it (visible on the real host filesystem) while denying reads outside it.

Connecting to Docker Model Runner

If the LLM backend is DMR instead of Ollama/llama-server, the same --add-host flag above is required, but the base_url path differs — DMR serves its OpenAI-compatible API under /engines/v1, not bare /v1:

[backend]
base_url = "http://host.docker.internal:12434/engines/v1"
model = "ai/smollm2:135M-Q4_K_M"

Confirmed live end-to-end (2026-09-21): a real chat completion round-trip through this exact path from inside a container, against a real DMR instance running on the host.

Can’t reach the host from the container

Symptom: curl from inside the container times out against host.docker.internal:<port>, while the same address works fine from the host itself. Cause, confirmed on one real machine: a host firewall (iptables/ufw) with a default-deny INPUT policy blocks the container→host-gateway path even though Docker’s own port-publish networking is correctly configured — Docker’s port-publish DNAT rule deliberately excludes traffic originating from the bridge network (docker0) to avoid a hairpin-NAT loop, so the packet is delivered locally instead of NAT’d, and a strict host firewall then drops it with no matching rule. Fix (scoped to exactly this traffic, on Linux):

sudo iptables -I INPUT -i docker0 -p tcp --dport <port> -j ACCEPT

This is a property of the host’s own firewall configuration, not something aivyx-coder’s Dockerfile or config can detect or fix — if a host.docker.internal-based connection times out (not “connection refused”), check the host firewall before assuming the backend is misconfigured. See docs/HISTORY.md’s Docker Model Runner chapter for the full diagnosis.