The BEAM Temptation: What Running Rails on Erlang's VM Would Actually Require
Source: lobsters
Sam Ruby’s recent post about Rails on the BEAM lands in a long-running conversation the Ruby community has been having with itself, mostly quietly, for about a decade. Elixir exists, Phoenix exists, and José Valim, who created both, was a Rails core team member. The lineage is not subtle. So when someone with Sam Ruby’s depth in the Rails world starts writing about the BEAM, it’s worth following the thread carefully.
The BEAM is the virtual machine that runs Erlang, Elixir, Gleam, and a handful of other languages. It was designed in the 1980s by Ericsson for telecom switches, and the design constraints of that problem shaped everything about it. Telecom switches cannot go down. They handle enormous numbers of concurrent sessions. They need to update their code without stopping. The BEAM solves all three of those problems at the VM level, which is why the concurrency story is so different from anything Ruby runs on today.
What the BEAM Actually Provides
The BEAM’s process model is the core of it. These are not OS threads or OS processes. They are lightweight green processes, each starting at around 2KB of heap, managed entirely by the BEAM scheduler. A single machine can run millions of them without meaningful overhead. Each process has its own heap, communicates only by message passing, and fails independently. The scheduler is preemptive: it measures execution in “reductions” rather than time, interrupting processes that run too long without yielding.
What this means in practice is that a slow database query in one process does not block the scheduler from handling incoming requests in another. There is no shared mutable state between processes unless you explicitly use ETS (Erlang Term Storage), which is the BEAM’s shared in-memory store. The fault tolerance model flows directly from this: OTP supervisors watch processes and restart them according to configurable strategies when they crash, so the standard advice for handling unexpected errors in Erlang is to let the process crash and let the supervisor restart it with clean state.
Hot code reloading is also built into the VM. The BEAM can load a new version of a module while the old one is still running, maintaining two versions simultaneously during the transition, which is why Erlang systems have uptimes measured in years.
None of this is magic. It is a coherent set of design choices that were made together, and they interact. The no-shared-state rule is what makes independent garbage collection per process feasible, which is what makes the GC pauses short and predictable, which is what makes the latency story good.
Ruby’s Concurrency Position in 2026
CRuby still has the Global VM Lock, now officially rebranded as the GVL (previously GIL). Threads exist, but only one runs Ruby code at a time. IO can release the GVL, so genuinely IO-bound applications do get real concurrency from threads. But CPU-bound work does not parallelize across threads within a single process.
Ruby 3.x introduced Ractors as the path toward true parallelism. Ractors are isolated execution contexts that cannot share most objects and communicate by sending copies or by transferring ownership of objects. The model is clearly inspired by the BEAM’s process model, but it sits awkwardly on top of a language where almost everything is mutable and references are shared freely. The Ractor isolation rules are complex, and most existing gems are not Ractor-safe. Adoption has been slow.
The Fiber Scheduler interface, introduced in Ruby 3.0, is different in character. It allows non-blocking IO on fibers without the GVL constraints being the primary concern. Libraries like Async and Falcon use it to run large numbers of concurrent fibers efficiently. This is closer to cooperative concurrency than the BEAM’s preemptive model, but for web serving it can be remarkably effective.
So Ruby has multiple partial answers to the concurrency question. None of them are as clean or as complete as the BEAM’s answer.
What “Rails on the BEAM” Could Actually Mean
There are a few distinct things the phrase could refer to, and they have very different levels of feasibility.
The most ambitious reading is implementing a Ruby runtime that compiles to BEAM bytecode, the way Elixir does. Elixir’s compiler produces .beam files, and Elixir processes are BEAM processes. A “Ruby on BEAM” in this sense would mean a Ruby compiler targeting the same bytecode. This is technically possible. Gleam is a statically typed functional language that targets the BEAM (and also JavaScript), demonstrating that new languages can be designed for the VM. The hard part is that Ruby’s semantics conflict with the BEAM’s model at several points. BEAM processes share no state and communicate by copying values. Ruby’s object model is built on mutable shared references. Threading those needles while preserving Ruby semantics would require either heavy restrictions on the Ruby subset you support or a translation layer that essentially reconstructs shared state on top of message passing.
A more moderate reading is running a Ruby interpreter inside a BEAM process, via a port or NIF (Native Implemented Function). You get the BEAM’s process supervision and hot code loading at the orchestration layer, but Ruby code still runs with all of its existing limitations inside that process. This is closer to the integration patterns that already exist between Elixir and other runtimes. It gets you some of the operational benefits of the BEAM without the fundamental concurrency model.
The most pragmatic reading, and the one closest to where the actual Rails ecosystem sits, is using the BEAM alongside Rails rather than instead of Ruby. Elixir services handling the real-time, high-concurrency parts of an application, with Rails handling the CRUD-heavy, convention-driven parts. This is not exotic; many teams already do it. The question is whether Rails as a framework can be refactored to delegate enough of its concerns to a BEAM-native layer that you get meaningful wins without rewriting everything.
The José Valim Precedent
The most relevant data point in this conversation is Elixir’s own origin. Valim was not trying to run Rails on the BEAM. He was trying to get the productivity and ergonomics of Rails on a platform that had the BEAM’s concurrency model natively. The result was a new language with its own framework, Phoenix, which explicitly borrows Rail’s conventions where it can: generators, a Router DSL that resembles Rails routes, Ecto as a structural analogue to ActiveRecord.
Phoenix’s LiveView is the clearest demonstration of what BEAM semantics make possible at the framework level. Each connected client gets a persistent BEAM process. State is maintained in that process across the lifetime of the connection. Updates are pushed as diffs. This architecture is essentially free in terms of implementation complexity because the BEAM’s process model handles the lifecycle and isolation. Building the same thing on top of a thread-per-connection or shared-memory model would be significantly more work.
The reason this matters for the Rails discussion is that it shows what you gain when the language and framework are designed together for the BEAM, rather than ported to it. Rails’ architecture assumes Ruby’s memory model. ActiveRecord’s lazy-loading behavior, the way Rails passes objects across middleware, the assumption that a request lives in a single thread with shared access to instance variables: all of it was designed for a world where sharing is the default. Porting that architecture to the BEAM without redesigning it gets you neither the BEAM’s benefits nor Rails’ full feature set cleanly.
What Rails Would Gain and Lose
If you could actually run Rails on the BEAM with faithful semantics, the gains would be real. Request isolation becomes natural rather than something you enforce through conventions. Long-running requests cannot starve other requests at the VM level. Supervision means a request that crashes does not take down the process handling other requests. Connection handling becomes genuinely concurrent.
The losses are less obvious but equally real. The Ruby gem ecosystem is enormous. Most gems are not written with Ractor safety in mind, and BEAM process isolation is stricter than Ractors. Any gem that holds global mutable state, which is many of them, requires careful handling. Rails itself has initializers, class-level configuration, and global registries throughout its architecture. Reshaping those to fit the BEAM’s process model is not a small refactoring job.
There is also the tooling question. Rails developers have decades of accumulated muscle memory around the Rails way: the test stack, the deployment toolchain, the debugging workflow. Moving to a different VM, even with surface-level Rails compatibility, disrupts that.
The Honest Probability
Full Rails on the BEAM, with real BEAM concurrency semantics and the existing Rails codebase, remains a research project more than a production path. The conceptual gap between Ruby’s shared-state object model and the BEAM’s isolated-process model is large enough that closing it requires either giving something up on the Ruby side or building an elaborate translation layer that probably negates some of the BEAM’s performance characteristics.
What is more likely to happen, and what seems to already be happening gradually, is that Rails continues to take inspiration from the BEAM world, Ractors improve and adoption increases, and teams run Elixir services alongside Rails for the parts of their systems where BEAM’s model is most valuable. That is less exciting than “Rails on the BEAM” as a headline, but it is how these things usually go. The ideas cross the boundary even when the code does not.
The conversation Sam Ruby is having is worth having, though. Articulating clearly what you want from a different runtime is often the first step toward either building it or realizing you already have it in a different form.