Learning to Love bin/dev with Ruby on Rails

Finally coming to terms with how great the bin/dev development experience can be with Ruby on Rails.
Chris Young
Chris Young
December 05, 2023

For the better part of 6 years we've been using custom scripts to clearly compensate for something lacking in the Ruby on Rails framework. From initializing projects, to running tests and ultimately launching the project for development.

At Harled Inc. we place great importance on an amazing developer experience (DX) and these scripts have been essential in going from zero to productive with very little cognitive load. However, the Ruby on Rails framework continues to in-source some of the great features that we historically developed on our own. The time has come to take another look at our DX and see where we can shed some weight.

Our DX Stack

First, let's quickly review how we set up our Ruby on Rails projects. We try to stick to the same stack as much as possible. We do this not only for our efficiency in delivering great work to clients, but also because we believe the stack we've picked is the best one. Both aspects have been battle tested through onboarding dozens of new staff and successfully transitioning several large scale production projects.

Here are the main ingredients:

  • MacBook Pro - We factored out hardware issues by consolidating the entire team on MacBooks. The theory is to limit the amount of surface area at the OS/hardware level. Worked really well until M1 came along ...
  • VS Code Dev Containers - We use dev containers exclusively. It is how we support new members going from zero to productive in hours without managing a complex set of provisioning scripts. This sits on top of Docker and docker-compose.
  • dx.sh - This is a custom script that we use to simplify development activities. I've included a sample of the commands below, however, the primary command is go, which is used to launch the project and get to work.
  • vite - We've moved to vite as our asset bundling solution which runs in a separate container to keep concerns isolated.

Combined, we've found this stack to be incredibly productive in supporting the development of a wide range of web application / SaaS offerings.

However, we're all for change and particularly change that reduces our cognitive load and the amount of code that we need to independently support. We want our brain power focused on building really great software, not booting the application for development. Enter the modern Ruby on Rails (7.1 and soon to be 8.0).

A Brief Look at our dx.sh Script

Our dx.sh script is placed in the root of all projects, and we typically configure VS Code to automatically source it in any new terminal. If for some reason it isn't sourced, loading it in the shell can be accomplished with source dx.sh.

The idea of the script is to remove cognitive load on the developer to accomplish simple and frequent tasks. For example, starting the server for development is done by typing go.

Here is an example of some of the commands:


The script that makes your developer experience magical.


+remote mode+

go      - start the app
console - open a rails console in app
sync    - pull the prod database
import  - import a local prod database

Clearly go saves some keystrokes, and it also avoids the inevitable "I can't connect to my server" when an individual forgets to bind to console really only simplifies the underlying command of bundle exec rails console which isn't used nearly as often and probably isn't too much to ask of developers.

Now, we get into some more juicy stuff with import and sync. Both are helpers for getting production or production-like information loaded into a local development environment for debugging. Just typing sync is a fast and easy way to get going on a new bug or story (or to flush your filesystem cache if you've forgotten to source dx.sh!). However, these commands are just rake tasks, and 99% of the code and logic exist in ruby.

Net, we love dx.sh and what it represents, however, there is an irony in it now vs just using the Ruby on Rails defaults and continually forcing decisions that avoid the "tweaks" that lead to the need for anything but the vanilla commands in the first place.

Just to be fair, the dx.sh script used to facilitate much of what VS Code dev containers does now, that is, it would run from your local OS terminal, manage docker-compose to start a project and then ensure all of your "remote" commands ran in containers vs on the local system. Not an excuse to still have it, but to clarify the time period it was invented during.

The Exciting Path Forward

The Ruby on Rails team has done a great job of investing in developer happiness, starting with the commands needed to get everything going in the first place (no, it isn't bundle exec rails server -b -p 3000).

In new Ruby on Rails projects, with particular css/js bundling options selection, the project will automatically create a new file called ./bin/dev, at the root of the project. This script is really simple, just run foreman with the settings in Procfile.dev. Previously we had stayed clear of the Procfile world as we wanted to try and compartmentalize everything into its own container.

Here is a pretty typical Procfile.dev for our projects now:

vite: bin/vite dev
web: env RUBY_DEBUG_OPEN=true bin/rails server -b -p 3000

When we start work, 99% of the time we can just run ./bin/dev and off we go. This will start vite to take care of css/js/images and then launch the Ruby on Rails server.

In terms of retrofitting this to older projects, the steps are pretty simple:

  1. Copy ./bin/dev from existing project or a rails new.
  2. Create Profile.dev with the necessary services for the project.
  3. Remove the vite container / service from our docker-compose config.

Debugging Caveat

Debugging does look a little different as foreman multiplexes the processes, and thus a regular debug command won't end up in an interactive state. You'll see it show up in the log, but it isn't actually listening to you.

No worries, the problem is solved by running rdbg -A in a separate VS Code terminal to connect to the debugger session.

More to Follow

We're excited to continue to simplify our development setup while maximizing our developer experience. Stay tuned for more blog posts coming that share information about some of our favourite features (like production data anonymization) and the full details of our VS Code Dec Container strategy.

As always, if you or your team are looking to chat Ruby on Rails development please take a moment to say hi!.

About the author

Chris Young

Chris is dedicated to driving meaningful change in the world through software. He has taken dozens of projects from napkin to production in fast yet measured way. Chris has experience delivering solutions to clients spanning fortune 100, not-for-profit and Government.