Skip to main content
FTL uses Docker containers as isolated execution environments for AI coding agents. Containers are persistent — keyed by project path — so subsequent tasks reuse the same container with warm dependency caches and agent history.

Container lifecycle

Creation and persistence

One persistent container per project path, identified by an MD5 hash of the absolute path:
The container ID is written to ~/.ftl/containers/<hash> on boot and read back on subsequent runs:
The file lock (fcntl.flock) prevents two concurrent ftl code invocations for the same project from claiming the same container.

Container reuse hierarchy

FTL checks three sources for an existing container, in order:
1

Disk persistence

Read ~/.ftl/containers/<hash> to find the container ID saved from a previous session (even after process restart).
2

In-process standby

Check the class variable DockerSandbox._standby_id for a container released earlier in the same Python process (interactive shell mode).
3

Create fresh

If no existing container is found, create a new one:
Containers run sleep infinity as their entrypoint, keeping them alive indefinitely. All work happens via docker exec.

Workspace initialization

On every task, /workspace is wiped and restored from the snapshot:
What persists vs. what resets:
FTL does not wipe /home/ftl/. If the agent installs malicious packages globally, they persist across tasks until you manually destroy the container.

Setup command

On fresh containers only, FTL runs the setup command from .ftlconfig to install project dependencies:
Common pattern:
The true at the end prevents a missing requirements.txt or package.json from failing the boot.
Setup only runs once per container. On warm reuse, packages are already installed in /home/ftl/.local/ and global node_modules.

Command execution

FTL provides two execution methods:

exec (blocking)

Blocks until the command completes. Returns (exit_code, stdout, stderr).

exec_stream (streaming)

Used for agent execution. Calls callback(line) for each line of output, enabling live progress display.

Environment variable injection

Shadow credentials and agent auth keys are written to /tmp/.ftl_env inside the container:
All commands source this file before execution:
Root commands (exec_as_root) do not source the env file. They run as the root user for system operations like installing the proxy CA.

Diff computation

FTL computes diffs inside the container to avoid host-side Python walking through Docker’s VirtioFS layer:
The diff script is a Python program that runs inside the container:
This approach is much faster than os.walk() on the host side, especially for large projects.

Node.js pre-warming

To eliminate the 5-8s cold-start penalty on first agent use, FTL runs a background thread that loads Claude Code into the Linux page cache:
By the time you see “Running agent…”, Node.js is already warm. The first task executes immediately with no cold-start delay.

Cleanup and standby

When a session ends, the container is not destroyed. It’s released for reuse:
The container ID remains in ~/.ftl/containers/<hash> and in the class variable for in-process reuse. Atexit handler:
Containers are never automatically destroyed. To manually remove a container:

Resource constraints

  • Memory: 2GB (hard limit)
  • CPU: 2 cores
  • Network: Bridge mode with host.docker.internal gateway for proxy access
These limits are not user-configurable. If the agent needs more resources, it will hit OOM or CPU throttling.

Image variants

FTL uses Docker Hub images based on the agent selection from ftl setup:
The image name is hardcoded in sandbox/docker.py:15:
In production, this is overridden by the CLI to match the selected agent. The Dockerfile includes all common dependencies: Python 3.11+, Node.js, git, curl, rsync, etc.