Container lifecycle
Creation and persistence
One persistent container per project path, identified by an MD5 hash of the absolute path:~/.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:
Setup command
On fresh containers only, FTL runs thesetup command from .ftlconfig to install project dependencies:
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)
(exit_code, stdout, stderr).
exec_stream (streaming)
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:
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: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:~/.ftl/containers/<hash> and in the class variable for in-process reuse.
Atexit handler:
Resource constraints
- Memory: 2GB (hard limit)
- CPU: 2 cores
- Network: Bridge mode with
host.docker.internalgateway for proxy access
Image variants
FTL uses Docker Hub images based on the agent selection fromftl setup:
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.