FLAMEDockerBackend

A FLAME backend that runs runners as Docker containers on the host machine via the Docker Engine API (Docker-out-of-Docker) — no cloud account, no Kubernetes, no external infrastructure required.

The parent app runs inside a container and provisions runners by talking to the host Docker daemon through the mounted socket. Runners are ordinary containers started from the same image, connected to the same user-defined network, and shut down automatically when idle.

Features

Installation

def deps do
[
{:flame_docker_backend, "~> 0.1.0"}
]
end

Configuration

Add to your config/runtime.exs:

config :flame, :backend, FLAMEDockerBackend
config :flame, FLAMEDockerBackend,
image: "my-app:latest",
network: "my_network"

Then add a FLAME.Pool to your application supervisor:

{FLAME.Pool,
name: MyApp.Runner,
backend: FLAMEDockerBackend,
min: 0,
max: 4,
idle_shutdown_after: 15_000}

Required options:

Optional options:

See Available configurations for examples.

Parent deployment

The parent must run as a distributed release on a user-defined Docker network with:

  1. Docker socket mounted-v /var/run/docker.sock:/var/run/docker.sock (or your platform path)
  2. Stable container name--name my-app-parent so Docker DNS matches the Erlang node hostname
  3. Same network as runners--network my_network (must match :network config)
  4. Shared cookie — set RELEASE_COOKIE on the parent; it is forwarded to runners automatically

Example:

docker run --rm \
--name my-app-parent \
--network my_network \
-v /var/run/docker.sock:/var/run/docker.sock \
-e RELEASE_COOKIE=my-secret-cookie \
my-app:latest

Docker socket paths by platform:

PlatformSocket path
Linux/var/run/docker.sock
WSL2/mnt/wsl/shared-docker/docker.sock
macOS (Docker Desktop)~/.docker/run/docker.sock

Mount it into the parent container with -v <host-socket>:/var/run/docker.sock.

Integration Examples

Basic Elixir Applications

See integration steps in minimal test app's README.

Phoenix Projects

See integration steps in phx_minimal test app's README.

Available configurations

Runner container options use Docker Engine API field names. Set them globally in config :flame, FLAMEDockerBackend, per pool via the backend tuple, or at runtime when starting a pool or FLAME.Runner.

Application config (config/runtime.exs):

config :flame, FLAMEDockerBackend,
image: "my-app:latest",
network: "my_network",
host_config: %{
"Memory" => 2_147_483_648,
"NanoCpus" => 2_000_000_000,
"Ulimits" => [%{"Name" => "nofile", "Soft" => 65_536, "Hard" => 65_536}]
},
mounts: [
%{"Type" => "bind", "Source" => "/data/models", "Target" => "/models", "ReadOnly" => true}
]

Per-pool overrides (static or dynamic values at startup):

{FLAME.Pool,
name: MyApp.GpuRunner,
backend:
{FLAMEDockerBackend,
image: "my-app:gpu",
network: System.fetch_env!("DOCKER_NETWORK"),
host_config: %{"Memory" => 4_294_967_296, "NanoCpus" => 4_000_000_000},
mounts: [
%{
"Type" => "bind",
"Source" => "/var/run/docker.sock",
"Target" => "/var/run/docker.sock",
"ReadOnly" => true
}
]},
min: 0,
max: 2,
idle_shutdown_after: 15_000}

One-off runner with custom container settings:

{:ok, runner} =
FLAME.Runner.start_link(
backend:
{FLAMEDockerBackend,
image: "my-app:latest",
network: "my_network",
cmd: ["bin/my_app", "start"]}
)

Pool-level backend options override application config. Wiring fields (Hostname, NetworkingConfig, FLAME_PARENT, etc.) are always set by the backend and cannot be overridden.

Testing

Unit tests (default):

mix test

Docker integration tests — build the test app images, start parent containers, and run FLAME.call via release RPC. Requires a running Docker daemon and the socket path detected by DockerAPI.default_socket_path/0:

mix test.docker

test_apps/minimal

A minimal test application for integration testing.

Run everything (from the project root):

./scripts/minimal/01_run.sh

The script builds the image, recreates the minimal_flame_docker_backend_test network, picks the Docker socket for your platform (Linux, WSL2, or macOS), and starts the parent container with IEx.

Optional arguments:

# custom command (FLAGS default omitted when CMD is provided)
./scripts/minimal/01_run.sh "bin/minimal remote"
# custom docker run flags and command
./scripts/minimal/01_run.sh "bin/minimal start_iex" "-it"

Test the FLAME backend in IEx:

# Spawns a runner container and executes the function there
Minimal.test_flame_backend_lambda()
# Or test manually:
FLAME.call(Minimal.Runner, fn ->
IO.puts("hey from remote")
System.get_env() |> dbg
{node(), self()}
end)
# Execute many tasks — observe that only max containers from the FLAME.Pool child spec are spawned:
(for _ <- 1..10, do: Task.async(fn -> Minimal.test_flame_backend_lambda() end)) |> Task.await_many(120_000)
# Or even more tasks:
(for _ <- 1..10_000, do: Task.async(fn -> Minimal.test_flame_backend_lambda(:infinity) end)) |> Task.await_many(:infinity)

Connect to the FLAME runner node:

# find CONTAINER_ID of the node you want to connect to remotely:
docker ps
docker exec -it $CONTAINER_ID bin/minimal remote

Watch Docker activity (in another terminal):

docker ps -a --filter "name=minimal"

Cleanup:

./scripts/minimal/02_cleanup.sh

Removes all containers matching minimal (parent and FLAME runners) and the test network.

test_apps/phx_minimal

A Phoenix test application with a LiveView UI for integration testing.

Run everything (from the project root):

./scripts/phx_minimal/01_run.sh

The script builds the image, recreates the phx_minimal_flame_docker_backend_test network, picks the Docker socket for your platform (Linux, WSL2, or macOS), and starts the parent container with IEx on port 4000.

Optional arguments:

# custom command (FLAGS default omitted when CMD is provided)
./scripts/phx_minimal/01_run.sh "bin/phx_minimal remote"
# custom command and docker run flags
./scripts/phx_minimal/01_run.sh "bin/phx_minimal start_iex" "-d"

Try the FLAME backend in the browser:

Open http://localhost:4000 and click Spawn FLAME task. Each click runs FLAME.call on a remote runner via start_async; completed colors are saved to the database and shown in the UI. Click rapidly to queue multiple tasks — the pool runs up to max concurrent runners.

Connect to the FLAME runner node:

# find CONTAINER_ID of the node you want to connect to remotely:
docker ps
docker exec -it $CONTAINER_ID bin/phx_minimal remote

Watch Docker activity (in another terminal):

docker ps -a --filter "name=phx_minimal"

Cleanup:

./scripts/phx_minimal/02_cleanup.sh

Removes all containers matching phx_minimal (parent and FLAME runners) and the test network.