LaunchKit
← All posts
· 11 min read · by The LaunchKit team · 1 views

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.

#rails #ruby #tooling

Comments

No comments yet. Be the first.

Only used to confirm and publish your comment. Never shown publicly, never shared.

Markdown: **bold**, `code`, ```fenced blocks```, > quotes, [links](url). HTML and images are not rendered.