Reproducible Rails development environments powered by Nix. Works on Linux (x86_64, aarch64) and macOS (Intel, Apple Silicon).
Two approaches are available depending on your needs:
- Centralized devShells — shared, zero-config, all projects use the same environment
- Per-project devenv — each project owns its dependencies, services, and lock file
If you don't have Nix installed yet:
# Linux or macOS — Determinate Nix Installer (recommended)
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
# Verify
nix --versionThe Determinate installer enables flakes and the nix command by default. If you used the official installer, add to ~/.config/nix/nix.conf:
experimental-features = nix-command flakes
Both drop you into a shell with packages available, but they serve different purposes:
nix develop— enters a devShell (development environment). Runs shell hooks, sets env vars, provides the full build environment. This is what you want for Rails projects.nix shell— adds packages to your PATH temporarily. No hooks, no env vars. Useful for quick one-off tools (nix shell nixpkgs#jqto getjqfor a moment).
# Enter the Rails devShell (hooks run, env vars set, Gemfile shadow active)
nix develop github:abstracts33d/nix-devshells#rails-ruby34
# Just get Ruby on PATH temporarily (no env setup)
nix shell nixpkgs#ruby_3_4nix develop .#shell-name # enter a devShell (without direnv)
nix shell nixpkgs#package # quick temporary access to a package
nix flake update # update all flake inputs to latest
nix flake lock --update-input nixpkgs # update a single input
nix flake show # list all outputs of a flake
nix store gc # garbage collect unused store paths
nix profile list # list packages installed via nix profiledirenv automatically loads the dev environment when you cd into a project. nix-direnv makes it fast by caching the evaluation.
If you prefer not to use direnv, you can always enter a shell manually:
# Centralized approach
nix develop github:abstracts33d/nix-devshells#rails-ruby34
# Per-project approach (from the project directory)
nix develop --impureThe rest of this section covers direnv setup for automatic shell loading.
Managed declaratively. If you're using nix-devshells, direnv and nix-direnv are typically already configured in your system or Home Manager config.
Minimal flake.nix for a nix-darwin setup with direnv and nix-direnv:
{
description = "macOS development machine";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
darwin.url = "github:LnL7/nix-darwin";
darwin.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = {nixpkgs, darwin, ...}: {
darwinConfigurations.my-mac = darwin.lib.darwinSystem {
system = "aarch64-darwin"; # or "x86_64-darwin" for Intel
modules = [
({pkgs, ...}: {
# Nix settings
nix.settings.experimental-features = ["nix-command" "flakes"];
nixpkgs.hostPlatform = "aarch64-darwin";
# System packages
environment.systemPackages = with pkgs; [
git
];
# direnv + nix-direnv
programs.direnv = {
enable = true;
nix-direnv.enable = true;
};
# Required by nix-darwin
system.stateVersion = 6;
})
];
};
};
}Bootstrap nix-darwin (first time only):
# From the directory containing the flake.nix above:
nix run nix-darwin -- switch --flake .#my-macAfter that, rebuild with:
darwin-rebuild switch --flake .#my-macbrew install direnv
nix profile install nixpkgs#nix-direnvAdd the shell hook to your shell rc file:
# zsh (~/.zshrc)
eval "$(direnv hook zsh)"
# bash (~/.bashrc)
eval "$(direnv hook bash)"Add nix-direnv integration to ~/.config/direnv/direnvrc:
source $HOME/.nix-profile/share/nix-direnv/direnvrcFirst, install Nix if you haven't already (see Install Nix above).
Then install direnv and nix-direnv via Nix:
nix profile install nixpkgs#direnv nixpkgs#nix-direnvAlternatively, direnv is available in most distro package managers (nix-direnv still needs Nix):
# Ubuntu / Debian
sudo apt install direnv
# Fedora
sudo dnf install direnv
# Arch
sudo pacman -S direnv
# Then install nix-direnv via Nix
nix profile install nixpkgs#nix-direnvAdd to your shell rc file:
# zsh (~/.zshrc)
eval "$(direnv hook zsh)"
# bash (~/.bashrc)
eval "$(direnv hook bash)"Add nix-direnv integration to ~/.config/direnv/direnvrc:
source $HOME/.nix-profile/share/nix-direnv/direnvrcdirenv allow # authorize the .envrc in current directory
direnv deny # revoke authorization
direnv reload # force reload (usually automatic)
direnv status # show current state and loaded .envrc pathA single shared environment per Ruby version. Projects reference it via .envrc — no files to add to the project repo.
Create a .envrc in your Rails project:
use flake "github:abstracts33d/nix-devshells#rails-ruby34"Then allow it:
direnv allowThat's it. cd into the project and the environment loads automatically.
You can also enter a shell manually:
nix develop github:abstracts33d/nix-devshells#rails-ruby34This drops you into a shell with everything loaded. Exit with exit or Ctrl-D.
| Shell name | Ruby | nixpkgs channel |
|---|---|---|
rails-ruby34 (default) |
3.4 | unstable |
rails-ruby33 |
3.3 | unstable |
rails-ruby32 |
3.2 | 24.11 (stable) |
rails-ruby27 |
2.7 | 22.11 |
rails-ruby26 |
2.6 | 21.05 |
Each Ruby version uses the nixpkgs channel that ships it with an ABI-compatible GCC toolchain.
./bootstrap-legacy.sh ~/dev/clients/myorg/project1 ~/dev/clients/myorg/project2
# or with a glob
./bootstrap-legacy.sh ~/dev/clients/myorg/*The script reads .ruby-version from each project and writes the correct .envrc.
- Ruby (version-pinned with ABI-compatible GCC)
- Node.js 22 + Yarn (Node.js 14 for Ruby 2.6/2.7)
- Native libs: PostgreSQL, libyaml, libffi, zlib, readline, openssl, libxml2, libxslt, ImageMagick
- Build tools: pkg-config, gnumake, gcc, rustc, cargo
- Dev tools: overmind, pgcli, iredis
- Environment: GEM_HOME/GEM_PATH/BUNDLE_PATH set to
.gems/, bundler build flags for pg and nokogiri - Gemfile shadow: Strips
ruby "X.Y.Z"from Gemfile so bundler accepts the nixpkgs patch version
- Zero config per project — just a one-line
.envrc, nothing committed to the project repo - Shared lock file — all projects on the same Ruby version use the same pinned dependencies
- Fast onboarding —
bootstrap-legacy.shsets up all projects at once - No flake.nix in project — no nix files to maintain per project
- No per-project customization — all projects on the same Ruby share the same packages
- No managed services — relies on system-level PostgreSQL and Redis
- Shared nixpkgs pin — updating the lock file affects all projects at once
- Extra packages need a fork — adding a project-specific dependency means editing this repo
Each project gets its own flake.nix + devenv.nix + .envrc. Full control over dependencies and lock file. Uses system-level PostgreSQL and Redis (same as the centralized approach).
Ruby 3.2+ only. Legacy Ruby (2.6, 2.7) is incompatible with devenv — old nixpkgs package structures conflict with modern devenv evaluation. Use the centralized approach for legacy projects.
cd ~/dev/myproject
nix flake init -t github:abstracts33d/nix-devshells#railsThis creates three files:
flake.nix— devenv flake with nixpkgs-unstabledevenv.nix— Rails config (Ruby, Node, packages, env vars).envrc—use flake . --impure
Edit devenv.nix to set your Ruby version (default is pkgs.ruby_3_4), then:
# Files must be tracked by git for Nix to see them
git add flake.nix devenv.nix .envrc
direnv allowYou can enter the devenv shell manually:
nix develop --impure./templates/rails/bootstrap.sh ~/dev/clients/myorg/project1The script reads .ruby-version and patches the Ruby package. Only works for Ruby 3.2+ — legacy Ruby projects should use the centralized approach.
Everything from the centralized approach, plus:
- Per-project lock file —
flake.lockpins your exact dependency versions - Customizable — edit
devenv.nixto add packages, env vars, or devenv modules
| File | Git | Notes |
|---|---|---|
flake.nix |
Track | Devenv flake definition |
devenv.nix |
Track | Project-specific config |
.envrc |
Track | Direnv integration |
flake.lock |
Track | Pinned dependency versions |
.devenv/ |
Ignore | Runtime state and caches |
.gems/ |
Ignore | Local gem install directory |
.direnv/ |
Ignore | Direnv cache |
.Gemfile.nix |
Ignore | Shadow Gemfile (auto-generated) |
- Full project isolation — each project owns its dependencies and lock file
- Customizable — add packages, env vars, devenv modules directly in
devenv.nix - Independent updates — update one project's nixpkgs without affecting others
- Portable — project carries its entire dev environment, works on any Nix machine
- More files in project —
flake.nix,devenv.nix,.envrc,flake.lock - Slower first load — devenv builds a full environment on first
cd(cached after) - Larger closure — each project has its own copy of packages in the nix store
.gitignorechanges needed — must unignoreflake.nix,.envrcif previously ignored
| Scenario | Recommendation |
|---|---|
| Quick setup, many similar projects | Centralized |
| Project needs specific packages or services | Per-project devenv |
| CI/CD needs reproducible builds | Per-project devenv |
| Legacy Ruby (2.6, 2.7) | Centralized only (devenv incompatible) |
| Shared team environment | Per-project devenv (committed to repo) |
| Personal dev machine, fast iteration | Centralized |
Both approaches can coexist. Migrate projects one at a time using templates/rails/bootstrap.sh.
devenv supports managed services (PostgreSQL, Redis) per project via process-compose. This means each project gets its own database instance with data stored in .devenv/state/ — no system-level postgres/redis needed.
To enable, add to devenv.nix:
services.postgres = {
enable = true;
listen_addresses = "127.0.0.1";
};
services.redis.enable = true;Then start services with:
process-compose up # foreground (TUI)
process-compose up -D # background (detached)
process-compose down # stop all
process-compose attach # reattach TUIThis is useful for full project isolation (different postgres versions, CI environments, onboarding new developers without system setup). The current template uses system-level services for simplicity.
For advanced usage, this flake exports lib.mkRailsShellFor and lib.mkRailsShell for building custom devShells in your own flake:
{
inputs.nix-devshells.url = "github:abstracts33d/nix-devshells";
outputs = {nix-devshells, ...}:
nix-devshells.lib.mkRailsShell {
ruby = "3.4";
node = true;
extraPackages = []; # add project-specific packages
extraEnv = {}; # add project-specific env vars
};
}x86_64-linuxaarch64-linuxx86_64-darwinaarch64-darwin