Running a Ruby on Rails Application in Nix
A minimal configuration for running a Ruby on Rails application in Nix
I’m loving Nix. I recently wrote about how I use it to manage my new Mac, and I’m starting to wrap my head around how it can be used, not just for dotfile/package management, but individual application configurations as well.
Ruby on Rails is somewhat famously complicated to get running, both locally, and in production. Many Rails developers just install every dependency locally, directly onto their machine - Homebrew has certainly made this easier - and part of what made Heroku so popular was how easy it was to deploy a Rails app.1
I recently had to pull down one of my older Rails apps (my Shopify app Reporty) and try to get it running on my new Mac. I decided to try Nix to get it working. This config is good for me, but it’s not necessarily a fully-featured, batteries-included approach to Rails on Nix. Hopefully you can take it and expand it as needed for your own projects.
Note: this assumes you already have Nix installed. Check out my starter macOS Nix config for some links to get started.
First, we need to initialize a new flake. This is a Nix-specific way of organizing your project. Notably, it generates a lockfile much like Gemfile.lock
for Ruby projects. Run this command:
This will create a flake.nix
file in your project directory. This file is where you’ll define your Nix configuration. Inside of the file, we’ll set up a shell environment for our project. Here’s what mine looks like:
A few things worth noting here:
- We’re using the
aarch64-darwin
system, which is the architecture of my new Mac. You can change this to whatever architecture you’re using. - We’re using the
allowUnfree
flag. This is a flag that allows you to install packages that aren’t in the NixOS package repository. This is useful for installing things likengrok
, which is a tool I use to test my app locally.
Now, we can create a shell.nix
file. This is where we’ll define the environment for our project. Here’s what mine looks like:
The shell.nix
file is where most of the setup happens. Here’s what it does:
- Installs all needed dependencies, like
ruby
,postgresql
,redis
, andnodejs
. - Uses the
shellHook
function to set up a PostgreSQL database. - Wraps all these steps in a
mkShell
function, which is a Nix-specific way of defining a shell environment.
The buildInputs
are packages from Nix’s package repository. These are the packages that are installed when you run nix-env -i <package>
. You can see the full list of packages here.
The good part about this is that these packages are already pre-compiled for your system architecture, so they are extremely quick to install. If you need to build a package manually - for instance, if you have a library that you need to compile against on your machine - you can use nativeBuildInputs
instead.2
Now, we can run nix develop
to enter the shell. This will take a few minutes, as it’s downloading all the dependencies. Once it’s done, you should be able to run bundle install
and rails server
to get your app running.
This was my minimal setup for running a Rails app locally in Nix. I haven’t deployed this config to a server yet - in production, at least - so I’ll cover that in a future post. But I enjoyed this approach because it declaratively sets up all the dependencies for my project, and due to the characteristics of Nix, the build is isolated from the other dependencies on my machine. Nice!
Footnotes
-
To be fair - this is something the Rails devs care about a lot. DHH has worked on Omakub, a wrapper around Ubuntu that installs all the needed dependencies for a fully-functional dev environment. And Kamal is a new tool that makes it easy to deploy Docker containers on remote servers. There’s a lot of work going on right now to improve the experience! ↩
-
Man in the arena moment! I began by using
nativeBuildInputs
for all of the dependencies, and wondered why it was so slow, particularly to compile Ruby. It seemed strange. I went and looked at the Nix docs, and realized that instead of compiling it by hand, I can rely on Nix having a pre-compiled version for myaarch64-darwin
architecture by usingbuildInputs
instead. ↩