Agent containers: one Docker run per CLI call

An agent container is a throwaway Docker container that runs one call of a coding CLI; with DEVFLOW_AGENT_CONTAINER=on, DevFlow starts 1 per OpenCode invocation, with all Linux capabilities dropped.

Why containers

A container gives each OpenCode call its own process space and its own network namespace, attached to an internal Docker network. The process runs with every Linux capability dropped, cannot gain new privileges and has its own resource limits, and the container disappears when the call ends.

How DevFlow starts one

When the operator sets DEVFLOW_AGENT_CONTAINER=on, every OpenCode invocation becomes docker run --rm -i, named after the invocation and labelled with the role, task, workspace and invocation ids. The flags include:

  • --cap-drop ALL and --security-opt no-new-privileges, so the process holds no Linux capabilities and cannot gain new ones;
  • --network set to an internal Docker network whose only way out is DevFlow's egress proxy;
  • a tmpfs at /tmp, plus the non-root user and resource caps that test runs get.

Container mode also forces the model relay on, so provider keys stay outside.

What is mounted

Writable mounts are the task worktree and its git data, plus a per-run config folder that OpenCode writes to at startup; that folder is private to its owner and deleted after the run. Read-only mounts carry the compiled skill, the JSON schema, prompt files, the task's attachments and the runtime volume at /opt/devflow.

The runtime volume holds the OpenCode binary, Node and the pinned Figma MCP package, so design links work the same way as on the host. Other tasks' worktrees and the DevFlow application folder are not mounted, so a call cannot reach them.

Environment and state

The environment arrives through an env file readable only by its owner. Model keys and the host PATH are removed, and the proxy variables carry the run's token.

HOME and the temporary folders live on the tmpfs, so their contents are discarded when the call ends.

Toolchain and commands

The container starts from the image of the repository the run works on: the subtask's repository first, then the task's primary repository, then DevFlow's universal default. In this mode the prompt shows test commands bare, without a Docker wrapper, and tells the model that dependencies are already installed.

Stopping and cleanup

On cancellation DevFlow runs docker kill by name, because killing the Docker client alone would leave the process running. At startup the API creates the internal network and removes containers left over from a crash, and it repeats that cleanup when a shutdown drain times out.

FAQ

Does the Claude Code backend run in DevFlow's agent containers?

No. Agent containers replace the Landlock step only for OpenCode runs; DevFlow's Claude Code backend runs under the Landlock sandbox.

What does DevFlow do when an agent container exits with code 137?

DevFlow treats an uncancelled exit 137, like exit codes 125, 126 and 127, as an agent environment error and fails the task with an Agent environment card instead of starting rework.