09 February 2024
Nearly 14 years ago, I wrote about setting up emacs for Rails development. Having a need for a new Rails app, I figured it was time to look at a more modern IDE to make things easier.
The last time I tried to install Rails on my mac, I got into all sorts of build and compile errors that made it painful to even get started. These days I try to use Docker to make setup and laptop moves easier.
Having a need for a new Rails app, I though it would be interesting to try a fully dockerized dev environment. Rubymine supports developing in such a way, and this post describes how to setup it up.
Without Ruby locally, and by extension Rails, the first challenge is to generate a new Rails app. Doing that is as simple as:
docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app --user $UID:$UID ruby:3.2.3 sh -c "gem install rails && /usr/local/bundle/bin/rails new shiny_new_app"
This uses the Ruby 3.2.3 base image to install Rails and generate the new application.
Next, you need a container which contains Ruby and all the gems needed to run the Rails application. This can be defined in a Dockerfile, at the root of the Rails project, which I have named Dockerfile.dev. With bind mounts, (ie mounting the source into the container), on Mac OS the file permissions are translated by Docker Desktop. If you are running on Linux or WSL2 using Docker directly, the container needs to run as your own user ID, otherwise files created inside the container on the bind mount will be owned by root. Much of this Dockerfile is taken from this blog post
# ARG defined before from, can only be used in FROM.
ARG RUBY_VERSION=3.2.3
FROM ruby:$RUBY_VERSION
# Needed if you need to install some OS / Container packages for Gems
# Adding some generally useful tools here
RUN apt-get update && apt-get install -y \
build-essential \
git \
netcat-traditional \
vim \
sudo
# Non root user. Pass in the uid and gid for your local user for build
ARG UID
ENV UID $UID
ARG GID
ENV GID $GID
ARG USER=ruby
ENV USER $USER
RUN groupadd -g $GID $USER && \
useradd -u $UID -g $USER -m $USER && \
usermod -p "*" $USER && \
usermod -aG sudo $USER && \
echo "$USER ALL=NOPASSWD: ALL" >> /etc/sudoers.d/50-$USER
# throw errors if Gemfile has been modified since Gemfile.lock
# RUN bundle config --global frozen 1
#
# Bundler and gem install here.
ENV LANG C.UTF-8
ENV BUNDLE_PATH /gems
ENV BUNDLE_HOME /gems
ENV BUNDLE_BIN /gems/bin
ARG BUNDLE_JOBS=20
ENV BUNDLE_JOBS $BUNDLE_JOBS
ARG BUNDLE_RETRY=5
ENV BUNDLE_RETRY $BUNDLE_RETRY
ENV GEM_HOME /gems
ENV GEM_PATH /gems
ENV PATH /gems/bin:$PATH
RUN mkdir -p "$GEM_HOME" && chown $USER:$USER "$GEM_HOME"
RUN mkdir -p /usr/src/app && chown $USER:$USER /usr/src/app
WORKDIR /usr/src/app
USER $USER
WORKDIR /usr/src/app
COPY Gemfile Gemfile.lock ./
RUN bundle install
# This was needed to get around an error with Rubymine, but I don't recall what.
#RUN mkdir -p /usr/local/bundle/bin && ln -s /usr/local/bin/bundle /usr/bin/bundle
CMD ["/usr/local/bin/bundle", "exec", "rails", "server"]
This Dockerfile uses the Gemfile from the Rails apps and installs all the required Gems, which includes Rails. At this stage, this container can run rails server
and hence your Rails app.
As the image has a few parameters, the easiest way to build it, is inline with a docker-compose definition, rather than the usual command (ignoring the arguments):
docker build . -f Dockerfile.dev -t <imageName>:<version>
Having the image, we now need a docker-compose.yaml file to be used by Rubymine. This is fairly simple:
services:
web:
build:
context: ./
dockerfile: ./Dockerfile.dev
args:
# Check these for your current user ID. They don't matter
# when running for Docker Desktop on Mac
- UID=1000
- GID=1000
image: testapp:1
volumes:
- .:/usr/src/app
ports:
- "127.0.0.1:3000:3000"
# Needed for debugging
- "1234:1234"
- "26166:26166"
command: tail -f /dev/null
Notice we map the localhost port 3000 to the host so we can access the Rails app locally. The other ports are for the Rubymine debugger.
The command is set to tail -f /dev/null
so the container start and the IDE can run the server and various commands within it, somewhat like a remote host.
The setup for Rubymine is the same as for Intellij Ultimate with the Ruby and Docker plugins. There is also a pretty nice youtube video from Jetbrains that walks through the setup and IDE features.
Open the project via File -> New Project From Existing Sources. When the import completes, the IDE may give a warning about "No Ruby Interpreter configured for the project. Before configuring the interpreter, we should confirm docker is working with the IDE.
Open View -> Tool Windows -> Services (command-8 / ALT-8 on Windows) to reveal the docker window. Confirm docker shows as connected.
Open the docker-compose.yaml, and using the small green arrows in the margin, start the environment
Access the project preferences (command-; / ctrl-alt-shift-s on Windows), and go to SDKs. Click +, "Add Ruby SDK", "Remote Interpreter or version manager". Choose docker-compose, select "web" as the service (from above docker-compose.yaml).
Now goto Project (still in project structure via command-;), and select the newly added remote interpreter.
The final thing to to check is that any commands run inside the established docker-compose container. Using "command-, / ctrl-alt-s on Windows", go to the general preferences and then Build, Execution, Deployment -> Docker -> Ruby Settings. Ensure "docker-compose-exec, run the project with docker-compose up if needed".
With that all done, you should be able to double press control to open the "run anything" menu and then run rails server, rake tasks, migrations, generators etc.