# FLAME: running part of a Phoenix app on short-lived infrastructure

> FLAME is an Elixir library from the Phoenix framework organisation that treats your entire application as a lambda, so a block of code can be wrapped in a call and executed in a temporary copy of the app, with the pool scaling runners up and down around it. It suits CPU heavy work that does not belong on a request path, and the unit of execution is the whole app, database connections included.

**phoenixframework/flame** — Fleeting Lambda Application for Modular Execution: auto scale parts of your existing app with a single function call

- Repository: https://github.com/phoenixframework/flame
- Stars: 1,182 · Forks: 68
- Language: Elixir
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/phoenixframework-flame

## The idea: the application is the lambda

The premise is stated as a question and then answered. Imagine if we could auto scale simply by wrapping any existing app code in a function and have that block of code run in a temporary copy of the app.

That is the FLAME pattern, and the name is the acronym for Fleeting Lambda Application for Modular Execution.

With FLAME, you treat your entire application as a lambda, where modular parts can be executed on short-lived infrastructure. The framing is what makes it different from a job queue with a separate worker application. There is no second image to build, no second supervision tree to configure and no second set of releases to deploy. The runner is the same application you already have, booted temporarily to do one piece of work.

The consequences of that choice run through the whole design. The work happens in a temporary copy of the app, which means the code inside the call has access to the same modules, the same configuration and the same database connections as the code that called it. It also means the cost of running one job is the cost of booting the whole app, and the library is built around making that acceptable by pooling and reusing the copies.

The project lives in the Phoenix framework organisation, which is a reasonable home for it because a pool of short-lived application instances is a very Phoenix shaped problem, and the package is published on Hex as flame with the documentation on hexdocs.

## The dependency and the supervision tree

Adding the library is a normal Hex dependency, with one caveat about the Erlang version.

```elixir
defp deps do
  [
    # For Erlang/OTP 26 and earlier, you also need Jason
    # {:jason, ">= 0.0.0"},
    {:flame, "~> 0.5"}
  ]
end
```

The comment about Jason is the kind of detail that saves a debugging session, because it is conditional on the runtime version and therefore invisible on a machine that has a newer one.

Then you start a pool in your supervision tree, typically from the application module. The example uses the Fly.io backend, which is what makes the runners land on other machines rather than on the local node.

```elixir
children = [
  {FLAME.Pool,
   name: MyApp.SamplePool,
   backend: FLAME.FlyBackend,
   min: 0,
   max: 10,
   max_concurrency: 5,
   idle_shutdown_after: 30_000,
   log: :debug}
]
```

The pool is a supervised child, like any other, which means it starts with the application and is restarted if it crashes. The name is a normal name, so more than one pool can exist in the same application, and the rest of the options are the policy.

The backend is the piece that decides where the runners go. The example uses FLAME.FlyBackend, and the choice is the difference between scaling across machines and scaling within one node, which for a Phoenix application on a container platform is usually the question you are actually asking.

## The pool options are the scaling policy

The five options in that pool specification are worth reading one at a time, because together they are the entire behaviour of the system.

min is 0, so the pool starts with no runners. Nothing is booted until there is work, which is what makes the short-lived model cheap for an application that only occasionally needs it.

max is 10, the ceiling on how many copies of the application exist at once. Since each copy is a full application instance, this number is also your cost ceiling and your database connection ceiling, and the two are usually related, because each copy opens its own pool of connections.

max_concurrency is 5, which is how many of those copies may be working at the same time. The distinction between max and max_concurrency is the interesting one. Max bounds the fleet, and max_concurrency bounds the simultaneous work, so a pool can hold warm runners that are not currently running anything, which is a way of paying for warm capacity in exchange for not paying the boot cost on every request.

idle_shutdown_after is 30_000, thirty seconds, which is how long an idle runner is kept before it is shut down. This is the setting that trades latency against cost, and 30 seconds is a fairly aggressive setting that suits work where a boot is quick relative to the job.

And log is :debug, which is a logging level rather than a boolean, so the pool's own activity is visible when you are looking for it.

The pool handles elastically scaling runners up and down, as well as remote monitoring of resources, and the moduledoc is where the fuller example usage is.

## The call interface, and the thumbnail example

The call interface is the point of the library, and the example is a real piece of work rather than a toy.

```elixir
def generate_thumbnails(%Video{} = vid, interval) do
  FLAME.call(MyApp.FFMpegRunner, fn ->
    # I'm runner on a short-lived, temporary server
    tmp_dir = Path.join(System.tmp_dir!(), Ecto.UUID.generate())
    File.mkdir!(tmp_dir)
    System.cmd("ffmpeg", ~w(-i #{vid.url} -vf fps=1/#{interval} #{tmp_dir}/%02d.png"))
    urls = VideoStore.put_thumbnails(vid, Path.wildcard(tmp_dir <> "/*.png"))
    Repo.insert_all(Thumbnail, Enum.map(urls, &%{video_id: vid.id, url: &1}))
  end)
end
```

The first argument names the module to run in, the second is the function to execute, and the function closes over the video struct and the interval, which are passed along automatically.

The comment inside the function is the whole point, and it is deliberately in the first person. The runner is on a short-lived, temporary server, and the code it runs is the same code the caller would have run, with the same database access, since the entire application is running there.

That is what makes the pattern different from a background job. In a conventional setup, the thumbnail generator is code in a worker application that happens to share a library with the web application, and if the two drift apart, the worker does something subtly different. Here the work is the application, so the query that reads the video and the insert that writes the thumbnail row are the same code paths the web app would use.

The example also shows the cost model, since the ffmpeg invocation is an external command run in a temporary directory, and the results are written back through the same repository the caller would have used.

## cast and place_child, the other two interfaces

The library provides three interfaces, and the README lists them with the use case for each.

FLAME.call/3 is for synchronous calls, where the caller waits for the result. That is the thumbnail example.

FLAME.cast/3 is for asynchronous casts, where you do not need to wait on the results. The name is borrowed from the way Elixir already describes this shape of work, and the practical difference from call is that the caller returns immediately and the work happens somewhere else.

FLAME.place_child/3 is used for placing a child spec somewhere to run, in place of DynamicSupervisor.start_child or Task.Supervisor.start_child and similar. This is the most interesting of the three, because it is a drop-in replacement for an existing call rather than a new pattern to adopt. An application that already starts a child process under a dynamic supervisor can move that start to FLAME and have the child run in a temporary copy of the application instead, with no change to the child spec itself.

So there are three different levels of commitment. You can wrap one function, you can fire and forget, or you can redirect an existing dynamic child. The third is the one that lets a team adopt the library incrementally, since it does not require restructuring the call sites.

The pool itself is the other half of the interface, and it is what does the elastic scaling of the runners up and down, along with the remote monitoring of resources.

## What the design costs, and where it fits

Two things are worth being clear about before adopting this.

The first is the unit of execution. The README says it directly, that the work happens in a temporary copy of the app, and that you can do any work inside the FLAME call because you are running the entire application, with the database connections and all. The consequences are that a runner occupies a full set of database connections while it exists, and that the boot cost of your application is paid on the way to the first unit of work. A pool with min set to 0 and a short idle shutdown keeps that cost proportional to the actual load, and the max and max_concurrency settings are the guard rails.

The second is that the work has to be work your application can do. If the job needs a different dependency set, a different runtime, or a different configuration from the web tier, then the whole premise of the pattern is that you do not have that, and a separate worker service is the better answer. The pattern is at its best when the job is slow and CPU bound and wants the same data as the request path, which is the case in the example.

On the state of the project, the repository is small and conventional, with the library under lib, the tests under test, a formatter configuration, a changelog and a mix file with a lock file. The dependency line in the readme is at the 0.5 series, and the recent releases are not published as GitHub releases, so the version in the mix file and the changelog are the reference rather than a tag list. The last push to the repository was on 2026-08-05.

The clearest way to evaluate it is to look at the pool specification in the readme and ask whether your job would still be cheap to run if the whole application booted for it. If the answer is yes, the machinery is a few lines of configuration. If the answer is no, you want a worker, and the library is telling you so.

## Conclusion

Adopt FLAME if you have a slow, CPU bound job inside a Phoenix application, such as video processing, and you want it to run away from the request path without maintaining a second deployable service. Do not adopt it for work that needs its own dependency set or its own scaling policy, because the unit of execution is the entire application rather than a worker. Before you start, read the pool options carefully, since min, max, max_concurrency and idle_shutdown_after together decide how many copies of your app exist and how quickly they disappear.

## FAQ

### What is FLAME?

FLAME stands for Fleeting Lambda Application for Modular Execution and is an Elixir library that lets you treat your entire application as a lambda, so that modular parts can be executed on short-lived infrastructure. With it you wrap a block of code in a call and that code runs in a temporary copy of the application.

### How do I add FLAME to a Phoenix project?

Add the flame dependency to your deps, with the version constraint the readme gives, and note that for Erlang/OTP 26 and earlier you also need Jason. Then start a FLAME.Pool in your supervision tree, typically in the application module, naming it and choosing a backend such as the Fly.io backend.

### What do the FLAME pool options control?

min is the number of runners kept booted, max is the ceiling on how many copies of the application exist, max_concurrency is how many may work at the same time, idle_shutdown_after is how long an idle runner survives before being shut down, and log sets the pool's logging level.

### What interfaces does FLAME provide?

FLAME.call/3 for synchronous calls, FLAME.cast/3 for asynchronous casts where the caller does not wait for the result, and FLAME.place_child/3 for placing a child spec somewhere to run, in place of DynamicSupervisor.start_child or Task.Supervisor.start_child.

## Sources

- [Issues](https://github.com/phoenixframework/flame/issues)
- [License: MIT](https://github.com/phoenixframework/flame/blob/main/LICENSE)
- [phoenixframework/flame on GitHub](https://github.com/phoenixframework/flame)
- [README](https://github.com/phoenixframework/flame/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/phoenixframework-flame
