RubyMine with Docker

Lately I’ve been experimenting with RubyMine. I’ve played around with it in the past, but always ended up back on vim for various reasons. This time I’m making a concerted effort to learn its feature set and make them work for me, and so far I’m thoroughly impressed.

Unfortunately I had some problems setting up Ruby/Rails projects running in Docker containers to work with RubyMine’s debugging features, so I’ve documented how I did it and some of the issues I ran into.

Setting up a Rails Project

Let’s start with a basic Rails API project with two containers: one for the app and one for the DB.

Dockerfile:

FROM ruby:3
RUN apt-get update -qq && apt-get install -y postgresql-client
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]

docker-compose.yml:

version: "3.9"
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - db

entrypoint.sh:

#!/usr/bin/env bash
set -e

rm -f /myapp/tmp/pids/server.pid

exec "$@"

The full code is available on GitHub.

Running docker-compose up and navigating to localhost:3000 shows me the standard Rails welcome page. Now stop those containers and open the project in RubyMine.

The Ruby Interpreter

The first step is to set the correct Ruby interpreter, which lives in our Docker container.

Head to Settings ▸ Languages & Frameworks ▸ Ruby SDK and Gems. Click the “+” and select “Docker Compose”. RubyMine should pre-fill most fields here, but you’ll need to select the “Service” — or container — that your rails server is running inside. In our case, it’s “web”. Click OK and you should see a couple of progress indicators fly by.

That interpreter will be added to the list1. Be sure to select it, then click “OK”.

Note that if you’ve already added remote interpreters for other projects, RubyMine might try to use one of those when you first open the new project. This means that another, unrelated docker container will be running, whose ports may conflict. Try to make a habit of double-checking this with docker ps and bringing them down before clicking “OK”. If anyone at JetBrains is reading, this would be a candidate for improvement.

If all is well, RubyMine will start your container and you should see a background task kick off to index your gems. If not, check that you’re not running any other containers that might conflict, and that you’ve told RubyMine to bring the containers up if they’re not running (see the “Ruby Docker Integration"settings page.

RubyMine will keep your main container and any it depends on running whilst the project is open. Note that it won’t stop them when closing the project.

Having set up the correct interpreter, you’ll get autocompletion, documentation and jump actions across your codebase and gems.

Debugging

Debugging support requires two gems to be added to your Gemfile:

group :development, :test do
  gem 'ruby-debug-ide'
  gem 'debase'
end

Now bundle and rebuild the container. You can have RubyMine do this for you, or simply docker-compose run web bundle.

You should now be set for debugging. Add a breakpoint somewhere by clicking in the gutter and start debugging by clicking on the little green bug icon.

When the breakpoint is hit, you’re presented with a rich, visual debugger.

Troubleshooting

Sadly, I ran into a lot of issues setting this up across different projects. Here they are, documented.

I’m not running Rails

When RubyMine detects Rails, it automatically sets up run/debug configurations. This may not be the case for other frameworks, or no framework at all. The fix is simply clicking “Add Configuration” and selecting a template; in the case of Sinatra, for example, I’d select “Rack”, modify any options and click “Create configuration”. Debugging should then work as normal, assuming you have the gems and other setup done.

I put up a sample Sinatra app that’s set up for debugging on GitHub.

Why do/don’t I need to map additional ports for debugging?

When bringing up your docker containers, RubyMine adds some overrides. See the full line when bringing up docker containers for Sinatra debugging:

/usr/bin/docker-compose -f /home/jamie/repos/experiments/sinatra-rubymine/docker-compose.yml -f /home/jamie/.cache/JetBrains/RubyMine2020.3/tmp/docker-compose.override.3.yml up --exit-code-from web --abort-on-container-exit web

It first loads my docker-compose file and then overrides values with its own docker-compose.override.3.yml:

version: "3.9"
services:
  web:
    command:
    - "sh"
    - "-c"
    - "/usr/local/bin/ruby /usr/local/bundle/gems/ruby-debug-ide-0.7.2/bin/rdebug-ide\
      \ --key-value --disable-int-handler --evaluation-timeout 10 --evaluation-control\
      \ --time-limit 100 --memory-limit 0 --rubymine-protocol-extensions --port 1234\
      \ --host 0.0.0.0 --dispatcher-port 26168 -- /usr/local/bundle/bin/rackup -o\
      \ 0.0.0.0 -p 9292 config.ru"
    environment:
      RM_INFO: "RM-203.7148.67"
      TEAMCITY_RAKE_RUNNER_MODE: "idea"
      TEAMCITY_RAKE_RUNNER_USED_FRAMEWORKS: ":test_unit :shoulda "
      RUBYMINE_TESTUNIT_REPORTER: "/opt/.rubymine_helpers/rb/testing/patch/testunit"
      TEAMCIY_RAKE_TU_AUTORUNNER_PATH: "/usr/local/lib/ruby/gems/2.7.0/gems/test-unit-3.3.4/lib/test/unit/autorunner.rb"
      ANSICON: ""
      RUBYLIB: "/opt/.rubymine_helpers/rb/testing/patch/common:/opt/.rubymine_helpers/rb/testing/patch/testunit:/usr/local/bundle/gems/debase-0.2.4.1/lib:/usr/local/bundle/gems/ruby-debug-ide-0.7.2/lib"
      TEAMCITY_RAKE_TU_TESTRUNNERMADIATOR_PATH: "/usr/local/lib/ruby/gems/2.7.0/gems/test-unit-3.3.4/lib/test/unit/ui/testrunnermediator.rb"
    ports:
    - "1234:1234"
    - "26166:26168"
    - "9292:9292"
    stdin_open: true
    volumes:
    - "/home/jamie/repos/experiments/sinatra-rubymine:/app:rw"
    - "/home/jamie/.cache/JetBrains/RubyMine2020.3/coverage:/tmp/coverage:rw"
    - "jetbrains_ruby_helpers_RM-203.7148.67:/opt/.rubymine_helpers/rb"
    working_dir: "/app"
volumes:
  jetbrains_ruby_helpers_RM-203.7148.67: {}

You may need to map ports 1234:1234 and 26166:26168 if you’re trying to handle this all manually.

Port conflict (1234, 26166)

If you run into this when starting debugging, it’s probably because your “Run/Debug” configuration is set to “docker-compose run” rather than “docker-compose exec”. This would attempt to map ports already in use (1324 and 26166 are used for debugging) in a new container.

My other containers aren’t running

In the example, the “web” container depends on the “db” container, so we know that bringing up “web” will always ensure “db” is up too. But RubyMine only really cares about the “web” container — this is where your gems are, where your server runs, and where debugging takes place. It’s what you chose when setting up the interpreter.

If you want to ensure that all containers are up, I suggest simply running docker-compose up in a terminal before opening the project in RubyMine.

Error running […] Cannot start debugger. Gem ‘ruby-debug-ide’ is not installed or […]

Assuming you’ve installed the gem, it looks like RubyMine doesn’t always keep gems in sync. I’m not sure how RubyMine handles synchronization, but you can fix it by re-syncing them under the Ruby SDK and Gems settings.

Error installing debug gems

I’ve run into this a few times:

Gem::InstallError: gzip error installing /usr/local/bundle/cache/debase-ruby_core_source-0.10.12.gem
An error occurred while installing debase-ruby_core_source (0.10.12), and Bundler cannot continue.
Make sure that `gem install debase-ruby_core_source -v '0.10.12' --source 'https://rubygems.org/'` succeeds before
bundling.

I think it’s something to do with installing the gem under Ruby 3. I can’t propose a fix; sometimes installing it manually worked, and sometimes it didn’t.


  1. Unfortunately this is a global list and there’s no way to add a project-specific interpreter, so it may get crowded here. ↩︎