Which Linux distro for Rails
The question behind "ruby on rails developer linux distro" is usually asked as if one distribution were better at Ruby than the others, and that framing is what makes it hard to answer. Ruby itself is identical everywhere, because on every distribution in this article you get it the same way: you compile it. What the distribution decides is the band of software underneath and beside Ruby, and that band is narrower and more consequential than the question implies.
Everything below was measured on 2026-09-27 against the official container images for each distribution, on an M2 Max under Docker Desktop, aarch64 except for Arch, whose official repositories are x86_64 only and which therefore ran emulated. Container userland is not a desktop, and where that matters I say so.
The distro's Ruby is never your Ruby
Here is what each package manager offers as ruby today:
| Distribution | ruby candidate |
|---|---|
| Ubuntu 24.04 LTS | 1:3.2~ubuntu1 |
| Debian 13 | 1:3.3+b1 |
| Fedora 43 | 3.4.10 |
| Arch Linux | 3.4.10-1 |
| Alpine 3.22 | 3.4.4-r0 |
Ruby 4.0.5 is not on that list and will not be for years on the two Debian-derived rows. The
interesting part is that it does not matter in the direction people assume. rails 8.1.3.1 declares
required_ruby_version of >= 3.2.0, so every one of those Rubies can run current Rails. The reason
nobody uses them is not compatibility, it is that you want to pick the Ruby per project and the
distribution wants to pick one Ruby for the whole machine. That argument was settled in favour of
rbenv, mise and friends a decade ago and no distribution has moved it since.
So "which distro has the best Ruby" is the wrong question, and dropping it leaves the real one.
What the distro actually decides: the library your Ruby is compiled against
A version manager gives you the same Ruby source on every machine. It does not give you the same
Ruby binary, because ruby-build links against whatever development headers the distribution has
installed. That is the whole mechanism and it is easy to demonstrate: run the identical command on
two distributions and ask the resulting interpreter what it is holding.
On Ubuntu 24.04, after apt-get install build-essential libssl-dev libyaml-dev zlib1g-dev
libffi-dev libreadline-dev, ruby-build 4.0.5 /out finished in 219 seconds with MAKE_OPTS=-j12,
and the Ruby it produced says:
ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [aarch64-linux]
OpenSSL 3.0.13 30 Jan 2024
0.2.5
On Fedora 43, after dnf install gcc make patch openssl-devel libyaml-devel zlib-ng-compat-devel
libffi-devel readline-devel, the same ruby-build 4.0.5 finished in 176 seconds and produced:
ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [aarch64-linux]
OpenSSL 3.5.8 25 Aug 2026
0.2.5
Same builder, same tarball, same revision hash, a crypto library two and a half years apart. The
OpenSSL on Ubuntu 24.04 LTS is stamped 30 Jan 2024 and is still 3.0.13 today, while Debian 13, one
release younger, is on 3.5. Whether that costs you anything depends on what your application does
over TLS. One measurable difference today: openssl list -kem-algorithms | grep -ci mlkem answers 0 on
Ubuntu 24.04 and 7 on Debian 13, so a Ruby built on Ubuntu 24.04 has no post-quantum key
encapsulation available to it at all. If nothing you talk to negotiates ML-KEM yet, that is a number
and not a problem. It is also the kind of thing that becomes a problem on somebody else's schedule.
The failure mode on the way there is worth knowing, because the error does not name the cause. With
only build-essential installed and no libssl-dev, ruby-build decides the system has no usable
OpenSSL and starts compiling its own. The last lines before it gives up:
==> Installing openssl-3.0.20...
-> ./config --prefix=/out/openssl --openssldir=/out/openssl/ssl --libdir=lib zlib-dynamic no-ssl3 shared -Wl,-rpath,/out/openssl/lib
-> make -j12
BUILD FAILED (Ubuntu 24.04 on aarch64 using ruby-build 20260924)
and in the build log it points at:
crypto/comp/c_zlib.c:36:10: fatal error: zlib.h: No such file or directory
36 | #include <zlib.h>
| ^~~~~~~~
Nothing in that mentions Ruby, and the file it names belongs to a library the user never asked to
build. The definition that decides this is one line in share/ruby-build/4.0.5, gated on
--if needs_openssl:1.1.1-3.x.x. The fix is to install the -dev packages before the version
manager, not after it fails.
The one that costs a day: Chromium on Ubuntu
This is the difference that shows up in a Rails repository rather than in a Ruby build, and it is the strongest single argument against Ubuntu as a Rails workstation right now.
Ubuntu 24.04 does not package Chromium. apt-cache policy chromium answers (none). There is a
chromium-browser package, and its description is the entire story:
Transitional package - chromium-browser -> chromium snap
Install it and you get a /usr/bin/chromium-browser that is not a browser. Asking it to render a
one-line HTML file, which is approximately what a Capybara system test asks of it:
$ chromium-browser --headless --no-sandbox --dump-dom file:///tmp/a.html
Command '/usr/bin/chromium-browser' requires the chromium snap to be installed.
Please install it with:
snap install chromium
exit=1
Debian 13, from the same family and the same apt, answers the same command with:
Chromium 154.0.8037.57 built on Debian GNU/Linux 13 (trixie)
<html><head></head><body><p>hi</p>
</body></html>
On a desktop Ubuntu with snapd running, snap install chromium works and you move on. The cost
lands everywhere else: any container, any CI image built FROM ubuntu, any WSL install, any server
where snapd is not running. That is where bin/rails test:system stops working, and the error the
developer sees is a Selenium message about a browser binary, several layers away from the packaging
decision that caused it. Fedora 43 ships chromium 153.0.8010.52 as a real rpm, Arch ships
153.0.8010.52, Alpine 3.22 ships 142.0.7444.59.
Everything else Rails shells out to
The rest of the distribution's job is supplying the C libraries a Rails application links or execs. Those versions are years apart and the spread follows release cadence exactly:
| Ubuntu 24.04 | Debian 13 | Fedora 43 | Alpine 3.22 | |
|---|---|---|---|---|
libpq dev |
16.15 | 17.11 | 18.0 | 17.11-r0 |
libvips dev |
8.15.1 | 8.16.1 | 8.18.3 | 8.16.1-r0 |
| Chromium | snap stub | 154.0.8037.57 | 153.0.8010.52 | 142.0.7444.59 |
| Valkey | 7.2.13 | 8.1.1 | 8.1.10 | 8.1.7 |
The libpq row is the one that stops work. The client tools travel with it, and pg_dump refuses
to read a server newer than itself. Ubuntu 24.04's client against a postgres:18 container:
pg_dump (PostgreSQL) 16.15 (Ubuntu 16.15-0ubuntu0.24.04.1)
pg_dump: error: aborting because of server version mismatch
pg_dump: detail: server version: 18.6 (Debian 18.6-1.pgdg13+2); pg_dump version: 16.15 (Ubuntu 16.15-0ubuntu0.24.04.1)
exit=1
That is a bin/rails db:structure:dump away from being your problem, and the answer is the PGDG
apt repository rather than the distribution's. libvips decides what Active Storage variants can
do, and Ubuntu's is three minor series behind Fedora's.
Fedora 43 is the only row here with no Redis at all. dnf info redis exits 1. The replacement is
valkey 8.1.10, and for a Rails app that is a non-event since the redis gem speaks to it
unchanged, but a dnf install redis in a provisioning script will fail on that machine and the
message will not suggest the alternative.
Alpine, and the thing I expected that was not true
The received wisdom is that Alpine means compiling nokogiri. That stopped being true. On
ruby:4.0.5-alpine, with no compiler in the image:
Successfully installed nokogiri-1.19.4-aarch64-linux-musl
Successfully installed pg-1.6.3-aarch64-linux-musl
Successfully installed sqlite3-2.9.6-aarch64-linux-musl
Successfully installed ffi-1.17.4-aarch64-linux-musl
Successfully installed google-protobuf-4.36.2-aarch64-linux-musl
The heavy gems now publish a musl platform build alongside the gnu one. What does not install is more interesting:
$ gem install nio4r --no-document
Building native extensions. This could take a while...
ERROR: Error installing nio4r:
ERROR: Failed to build gem native extension.
current directory: /usr/local/bundle/gems/nio4r-2.7.5/ext/nio4r
/usr/local/bin/ruby extconf.rb
checking for unistd.h... *** extconf.rb failed ***
I went looking for the musl explanation and did not find one, because ruby:4.0.5-slim, which is
Debian and glibc, fails on exactly the same four gems: nio4r, bcrypt, msgpack and sassc. They publish
no platform gem for either libc, and neither slim image carries a toolchain: which make gcc cc
returns nothing on both. gem install puma dies inside gems/nio4r-2.7.5 on both, and bcrypt gets
the same make failedNo such file or directory - make on both. So the compile-everything tax is not
an Alpine tax, it is a minimal-base-image tax, and it is character for character identical on
Debian.
The performance claim is also smaller than its reputation. An allocation-heavy loop of 200,000 string and hash constructions, three runs on each image, same host, nothing else running:
ruby:4.0.5-slim 0.732 0.741 0.746
ruby:4.0.5-alpine 0.811 0.765 0.796
About 8 percent, consistent across runs. Note the condition: these are two different official Ruby builds, not one build swapped between libcs, so compiler and flags differ too and 8 percent is an upper bound on what musl itself costs here. It is not the 2x that gets repeated.
The dead end: I thought this was a glibc problem
Having built Ruby 4.0.5 inside Fedora 43, I mounted the tree into Debian 13 to compare the two interpreters side by side. It would not start:
/opt/ruby/bin/ruby: error while loading shared libraries: libcrypt.so.2: cannot open shared object file: No such file or directory
exit=127
I spent a while on the wrong theory. Fedora 43 is on a newer glibc than Debian 13, so the obvious
reading is that the binary needs a symbol Debian does not have, which is the usual reason a binary
built on a fast-moving distribution will not run on a slow one. That reading is wrong, and
objdump -T says so in one line: the highest versioned glibc symbol in bin/ruby and
lib/libruby.so.4.0 together is GLIBC_2.38, and Debian 13 ships 2.41.
The actual cause is smaller and more annoying. Both distributions ship libxcrypt, at different
sonames: Fedora 43 has libxcrypt-4.5.2-1.fc43 providing libcrypt.so.2, Debian 13 has libcrypt1
at 1:4.4.38-1 providing libcrypt.so.1. The same library, one major apart, and a Ruby build picks
up whichever it found. One symlink and the Fedora-built Ruby ran on Debian and printed its version. The
lesson I took is not about distributions, it is that a cannot open shared object file naming a
soname is never a glibc version problem, whatever the release dates suggest, and objdump -T settles
it in five seconds.
So, which one
Debian 13 or Fedora 43, and I would take Fedora if the machine is a workstation and Debian if it is also going to be a server.
Nothing above is a close call. Debian 13 and Fedora 43 both give you a real Chromium, a libpq at
most one major behind a current PostgreSQL server, an OpenSSL branch that is still getting features,
and a libvips from the current series. Ubuntu 24.04 gives you none of those four, and the Chromium
one is not a version lag you can wait
out, it is a packaging decision that makes bin/rails test:system fail in every context without
snapd. Arch is current on every row measured here and its cost is the one Arch always charges: you
are the one who notices when a rolling update moves a shared library out from under a compiled Ruby,
and you will rebuild it.
What would change my answer: Ubuntu 26.04 LTS putting a real chromium deb back in the archive, or
shipping OpenSSL 3.5. Either one closes most of the gap. Until then, if you are on Ubuntu LTS and it
works for you, the honest advice is not to reinstall; it is to know that those four packages are the
ones that will bite, and that snap install chromium is doing more work for you than you think.
What this page does not cover
Desktop concerns: Wayland, fractional scaling, GPU drivers, battery life, whether your editor's tray icon works. Those decide a workstation distribution for most people and none of them were measured here, because containers cannot measure them.
It also does not cover WSL2, NixOS or Omarchy. Nix answers this whole question differently by taking the C libraries away from the distribution, which makes it a different article rather than another row in the table. Omarchy is Arch underneath, so the package rows apply, but its own defaults were not tested. No number here is from a physical Linux machine; every measurement is a container on macOS, and anything decided by the host kernel, the filesystem or the scheduler would need re-running on real hardware before it meant anything.
Comments
No comments yet. Be the first.