New greener region discount. Save 3% on Upsun resource usage. Learn how.
LoginFree trial
FeaturesPricingBlogAbout us
Blog

How to start a Ruby on Rails project with devenv.sh

RubyNixRailsdevenvMySQL
31 May 2024
Laurent Arnoud
Laurent Arnoud
Senior Software Cloud Engineer

In this article, we are going to take a look at the process of pushing a Ruby on Rails project to Upsun using the power of devenv.sh

Devenv is a local development tool, similar to Docker Compose but without the container build step, meaning it's faster and reproducible as it uses Nix, a powerful package management and system configuration tool. If you are a beginner at Rails and want to try out Nix tooling as a development environment without diving too deep, it’s faster to do so on your Linux and MacOS compared to docker-compose or devbox since both focus on containers. And as it already has a lot of languages and services support built-in, Nix is a pretty easy tool to test out. 

How to use devenv.sh with Ruby on Rails 

This first step is to follow your favorite install method to install devenv.sh—I recommend using a flake which allows you to just add experimental-features = nix-command flakesto ~/.config/nix/nix.conf

Once you’ve successfully installed devenv.sh, it’s time to create a new Rails project by using the following command—we can skip the bundle since we will handle it on devenv later.

> rails new rails-7-mysql --database=mysql --skip-bundle

Then, create the base files with this command:

> devenv init
• Creating .envrc
• Creating devenv.nix
• Creating devenv.yaml
• Creating .gitignore

As you can see devenv will generate a .envrc for direnv to set up automatic shell activation when you go into your project and the devenv.nix will contain the project definition—more about it later.

For now, you can start adding some packages to the devenv.nix:

# https://devenv.sh/packages/
packages = with pkgs; [
  git
  ruby_3_3
  pkg-config
  libyaml.dev
  openssl.dev
];

Please note: in this article, we will only cover the standard Rails assets, if you are using a different assets framework please refer to its documentation.

The next step is to set things up for when you enter the devenv shell. When you go into your project directory it will load the environment it needs to replace the PATH with nixpkgs instead of locals and run the commands defined in there. I prefer to run `bundle` every time to install the project dependencies and ensure we always have the latest dependencies fetched and don’t run into any trouble when we switch to the devenv environment. 

enterShell = ''
    git --version
    ruby --version
    bundle
'';

# https://devenv.sh/languages/
languages.ruby = {
  enable = true;
  package = pkgs.ruby_3_3;
};


If gems start to fail—an example of an error on build can be seen below—you might need to install postgresql or libmysqlclient to resolve it:

W: Gem::Ext::BuildError: ERROR: Failed to build gem native extension.
W:
W: 	current directory: /app/vendor/bundle/ruby/3.2.0/gems/psych-5.1.1/ext/psych
W: /nix/store/7apky1wg5v258lk066d6xw1g3ddmy6dn-ruby-3.2.2/bin/ruby extconf.rb
W: checking for yaml.h... no
W: yaml.h not found
W: *** extconf.rb failed ***
W: Could not create Makefile due to some reason, probably lack of necessary
W: libraries and/or headers.


From the terminal, you can find the right package by starting nix interactive shell nix repl --file ‘<nixpkgs>’ and exploring packages outputs with libmysqlclient.outputs. Usually dev output will contain headers needed on compiled gems, for example libyaml.dev for psych Ruby YAML parser gem. 

From the browser, go to https://search.nixos.org/packages which also shows the outputs—most of the time it's dev and can be added to the packages list into devenv.nix.

Once you get your bundle complete, firstly congrats, it can be tricky sometimes with dependencies, and you can now move on to services.

For a MySQL database, don't forget to add libmysqlclient.dev to the packages list, or else the mysql2 gem won't compile:

services.mysql = {
  enable = true;
  package = pkgs.mysql;
  initialDatabases = [{ name = "rails_7_mysql_development"; }];
  ensureUsers = [
    {
          name = "root";
          password = "";
          ensurePermissions = {
            "rails_7_mysql_development.*" = "ALL PRIVILEGES";
          };
    }
  ];
};

To have the rails server start when using devenv up we just need to add the command as follows to the processes:

processes.rails.exec = "rails server";

At this point, you should be able to develop locally, update your Gemfile.lock, and run database migrations and tests. Enjoy the fast setup and development!

How to push to Upsun

To generate the basic Upsun config you can run upsun project:init, this will ask you some questions and generate .upsun/config.yaml. We are in the process of integrating the Rails stack to the project:init command so you will need to add some things to the config as you can see below.  Don't forget to add to Git the .environment file and remove it to the default Rails .gitignore.

Welcome to Upsun!
Let's get started with a few questions.

We need to know a bit more about your project. This will only take a minute!

What language is your project using? We support the following:
Use arrows to move up and down, type to filter
  C#/.Net Core
  Elixir
  Go
  Java
  Lisp
  JavaScript/Node.js
  PHP
  Python
> Ruby
  Rust
—
Select all the services you are using:
Use arrows to move, space to select, type to filter
> [x]  MariaDB
  [ ]  MySQL
  [ ]  PostgreSQL
  [ ]  Redis
  [ ]  Redis Persistent
  [ ]  Memcached
  [ ]  OpenSearch


On hooks, you will need to do the following:

hooks:
  build: |
    set -eux
    bundle install
    bundle exec rails assets:precompile
  deploy: |
    set -eux
    bundle exec rake db:migrate

On the variables side, you will need to do the following:

variables:
  env:
    PIDFILE: "tmp/server.pid"
    RAILS_ENV: "production"

Last thing is the mounts for logs, tmp, and storage:

mounts:
  "log":
    source: "tmp"
    source_path: "tmp"
  "storage":
    source: "storage"
    source_path: "storage"
  "tmp":
    source: "tmp"
    source_path: "tmp"

Once that is complete, you can create a project with upsun project:create and upsun push. You should then see the default Rails index when everything goes well. Hooray!

You can always find more info on our docs.

Thank you to Eder Leão Moosmann and Paul Gilzow for contributing to this post.

Upsun Logo
Join the community