Set up a Rails Development Environment with Dev Containers

Ask questions Research chat →

https://spin.atomicobject.com/rails-dev-containers/ · scraped

rails deploy

Attachments

Scraped Content

— 1020 words · 2026-02-14 17:41:13 UTC ·

Excerpt

![](https://spin.atomicobject.com/wp-content/uploads/BryanElkus_2024-02-24_0431-scaled.jpg) I recently worked on rewriting a super old Rails app (Rails 2 + Ruby 1.8.7). We felt a bit burned by the fact that we couldn’t even install such an old version of Ruby to run the old app. So, we decided to start fresh and prioritize setting up a dockerized development environment. Here, I’ll walk through the steps we followed to set up a pretty nice-to-work-with Rails development environment using Docker and VS Code Dev Containers. ## 1. Adding an Initial Dockerfile + Compose File For this example, let’s say that we are working in the shiny-project repo. This is the initial Dockerfile we create. Since we are creating a new Rails project, all we have to do at this point is specify that we want Ruby for our base image. ```plain text FROM ruby:3.3.0-bookworm RUN mkdir -p /shiny-project WORKDIR /shiny-project EXPOSE 3000 ``` And here is the compose.yml file. We are mounting the entire shin
![](https://spin.atomicobject.com/wp-content/uploads/BryanElkus_2024-02-24_0431-scaled.jpg) I recently worked on rewriting a super old Rails app (Rails 2 + Ruby 1.8.7). We felt a bit burned by the fact that we couldn’t even install such an old version of Ruby to run the old app. So, we decided to start fresh and prioritize setting up a dockerized development environment. Here, I’ll walk through the steps we followed to set up a pretty nice-to-work-with Rails development environment using Docker and VS Code Dev Containers. ## 1. Adding an Initial Dockerfile + Compose File For this example, let’s say that we are working in the shiny-project repo. This is the initial Dockerfile we create. Since we are creating a new Rails project, all we have to do at this point is specify that we want Ruby for our base image. ```plain text FROM ruby:3.3.0-bookworm RUN mkdir -p /shiny-project WORKDIR /shiny-project EXPOSE 3000 ``` And here is the compose.yml file. We are mounting the entire shiny-project directory to sync any changes to the repo between our host machine and the app container. Your database service + volume will also be defined in this same compose file. ```plain text version: '3' services: app: build: context: '.' dockerfile: 'docker/Dockerfile' stdin_open: true tty: true entrypoint: /shiny-project/docker/script/entry.sh volumes: - .:/shiny-project ports: - '3000:3000' ``` Here are the contents of the entry.sh script called out as the app service’s entrypoint. shiny-app is what we will eventually call the rails app we create. This script just keeps the container alive indefinitely, listening for any commands to run, and able to respond to any kill signals we send. Bash ```plain text #!/bin/bash mkdir -p /shiny-app/tmp mount -t tmpfs tmpfs /shiny-app/tmp _term() { echo "Caught term, send kill" kill -SIGTERM "$child" 2>/dev/null } _int() { echo "Caught int, send kill" kill -SIGINT "$child" 2>/dev/null } trap _term TERM trap _int INT echo 'APP READY!' sleep infinity & child=$! wait $child exit 0 ``` Now, we can run docker-compose up -d to start our services. ## 2. Creating the Rails App We need to exec into the app container to create our new Rails app, To install rails in the container: Bash ```plain text docker-compose exec app gem install rails ``` To generate a new Rails app: Bash ```plain text docker-compose exec app rails new shiny-app ``` ## 3. Improving the Dockerfile Now that we actually have a Rails app generated, we can take another pass at improving the Dockerfile. We set the working directory to that of the Rails app to make running Rails CLI commands easier. And copy the Gemfile and Gemfile.lock into the image so that we don’t have to install Gems manually. ```plain text FROM ruby:3.3.0-bookworm RUN mkdir -p /shiny-project/shiny-app WORKDIR /shiny-project/shiny-app ADD ./shiny-app/Gemfile /shiny-project/shiny-app/Gemfile ADD ./shiny-app/Gemfile.lock /shiny-project/shiny-app/Gemfile.lock RUN gem install bundler:2.5.3 RUN bundle install EXPOSE 3000 ``` ## 4. Adding Convenience Scripts Next, we can add a handful of scripts to make it easier to run Rails commands from our host machine’s terminal. We added the following four scripts to the shiny-project/bin directory. dx – shorthand for us to exec into the app container. Bash ```plain text #!/bin/bash docker-compose exec app "$@" ``` drails – shorthand for running Rails CLI commands. Bash ```plain text #!/bin/bash SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) $SCRIPT_DIR/dx bundle exec rails "$@" ``` drake – shorthand for running Rake tasks. Bash ```plain text #!/bin/bash SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) $SCRIPT_DIR/dx bundle exec rake "$@" ``` dserver – shorthand to run the Rails server. Bash ```plain text #!/bin/bash SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) $SCRIPT_DIR/dx bundle exec rails s -b 0.0.0.0 "$@" ``` With Direnv installed and PATH_add ./bin added to the .envrc, the following two commands are equivalent: Bash ```plain text docker-compose exec app bundle install dx bundle install ``` ## 5. Creating a VS Code Dev Container Next, we will set up a VS Code Dev Container definition to allow us to optimize our IDE for Rails development more easily. Add a new file, .devcontainers/devcontainer.json, that contains the following: Json ```plain text ```json { "name": "Existing Docker Compose (Extend)", "dockerComposeFile": ["../compose.yml"], "service": "app", "workspaceFolder": "/shiny-project", "customizations": { "vscode": { "extensions": [ "Shopify.ruby-extensions-pack", "Hridoy.rails-snippets", "aliariff.vscode-erb-beautify" ] } } } ``` We are just making use of our existing compose.yml app service, which already handles mounting project files and running a command to keep itself alive. We also call out a handful of VS Code extensions for Rails development that the Dev Container should install automatically. If any of these extensions require globally installed gems, we can install those as an Then, with the Dev Containers extension installed and our app container already up and running, select the “Dev Containers: Reopen in Container” command. ## 6. Configuring the VS Code Workspace You will have noticed that we have our Rails app nested inside of the top-level shiny-project folder. We chose this directory structure because we didn’t want to junk up our Rails production image with the random scripts, doc, etc. at the top of the repo. But, many extensions we added don’t like this organization and really want the Rails app up at the root. So to get the best possible editor support, the final thing we add is a shiny-project.code-workspace to help handle this: Json ```plain text { "folders": [ { "path": "../shiny-project" }, // We needed to bring the shiny-app folder up to the root level so the Ruby LSP extension will work properly. { "path": "shiny-app" } ] } ``` With this setup, we can easily run any of the Rails CLI commands from our terminals and have a highly customized IDE configuration. Plus, we are doing this all without ever having to install Ruby or any global Ruby gems on our own dev machines. And, our normal VS Code configuration does not have to get junked up with all the Ruby/Rails extensions and settings. Most of all, we hope this setup will help new developers hit the ground running when they pick it back up again in the years to come. dockervs coderails

Visibility

Visible to everyone

Reading Status

Related Bookmarks

My Note


Saved!

Annotations

Export as Markdown
+ Annotate selection

Add Annotation