Running old Ruby on Rails application with Docker
A few years ago, in 2016 I was developing Ruby on Rails applications. It was the beginning of my programming journey so I was not paying attention to containerization or even proper documentation. This made running those apps in 2022 a little bit harder.
As an example, I'll show the process based on my euro2016 repository. Looking at the Gemfile you can see that there are not many dependencies there - the process should be easier than a real commercial big application. The website was created using rails 4.1.8 which was released in 2014! Almost 10 years ago.
First try: running on host
Firstly I thought maybe it's possible to install and run the app directly on my computer (mac). I installed ruby and ran bundle install
. Sadly it was not so easy. Some old dependencies were causing problems.
euro2016 on master [?] via 💎 v2.6.8
✗ bundle install
An error occurred while installing json (1.8.3), and Bundler cannot
continue.
Make sure that gem install json -v '1.8.3' --source 'https://rubygems.org/'
succeeds before bundling.
In Gemfile:
rails was resolved to 4.1.8, which depends on
actionmailer was resolved to 4.1.8, which depends on
actionpack was resolved to 4.1.8, which depends on
actionview was resolved to 4.1.8, which depends on
activesupport was resolved to 4.1.8, which depends on
json
Having this error I tried to install the newer version of json
gem but new problems arrived. Foremost I wanted to run the app in the same state as it was before. Upgrading dependencies might change some behavior and I didn't want that.
After some googling, a few hours spend tinkering with the build process, and even changing versions there was no progress. As a next step I tried using rbenv. I thought installing an older version of ruby may solve some problems but installing the older version caused even more problems in the first place. Some dependency was not working because some os-package was updated (like libssl or something simillar). I didn't want to install old packages on my OS and here comes the Docker.
Second try: containerization with Docker
Using Docker for Ruby on Rails is pretty straightforward. You use ruby
image and that's it. As a first iteration I tried the basic Dockerfile
:
FROM ruby
WORKDIR /root
ADD Gemfile /root
ADD Gemfile.lock /root
RUN bundle install --without production
We use ruby:latest
image here with some Ruby 3 version. Copying Gemfile
and Gemfile.lock
and running bundle install
. So what was the problem with this approach?
#9 39.00 @package.dir_mode = options[:dir_mode]
#9 39.00 ^^^^^^^^^^^
#9 39.04 An error occurred while installing rake (11.1.2), and Bundler cannot continue.
#9 39.04 Make sure that `gem install rake -v '11.1.2'` succeeds before bundling.
------
executor failed running [/bin/sh -c bundle install --without production]: exit code: 5
Then I thought maybe the latest version of ruby is not the best. I didn't know what version of ruby I was using in 2016. I should have documented it and made my life easier. As a wild guess I tried ruby:1.9.3 and got the following error:
#0 34.15 Installing actionpack 4.1.8
#0 34.57
#0 34.57 Gem::InstallError: mime-types-data requires Ruby version >= 2.0.
#0 34.58 An error occurred while installing mime-types-data (3.2016.0521), and Bundler
#0 34.58 cannot continue.
#0 34.58 Make sure that `gem install mime-types-data -v '3.2016.0521'` succeeds before
#0 34.58 bundling.
------
Ok, some package required Ruby version >= 2.0. After a few more tries I managed to find the proper version which is Ruby 2.1.
So all dependencies were installed correctly. To simplify the process I created docker-compose.yml
which is used simply to build and start (serve) the application:
version: '3'
services:
rails-app:
build:
context: .
dockerfile: Dockerfile
image: euro2016-rails
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
ports:
- '3000:3000'
working_dir: /root
volumes:
- .:/root:cached
I tried to serve the application but the new problem (or a challenge) arrived:
[+] Running 1/1
- Container euro2016-rails-app-1 Recreated 0.1s
Attaching to euro2016-rails-app-1
euro2016-rails-app-1 | /usr/local/lib/ruby/gems/2.1.0/gems/bundler-1.15.1/lib/bundler/runtime.rb:85:in `rescue in block (2 levels) in require': There was an error while trying to load the gem 'uglifier'. (Bundler::GemRequireError)
euro2016-rails-app-1 | Gem Load Error is: Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes.
euro2016-rails-app-1 | Backtrace for gem load error is:
euro2016-rails-app-1 | /usr/local/bundle/gems/execjs-2.7.0/lib/execjs/runtimes.rb:58:in `autodetect'
Oh no, we don't have JavaScript runtime. That means we need to install nodejs
. It will be easy, I have to add only apt-get install nodejs
to the Dockerfile
, right? Actually no.
=> ERROR [6/6] RUN apt-get install -y --force-yes nodejs 3.0s
------
> [6/6] RUN apt-get install -y --force-yes nodejs:
#0 1.303 Reading package lists...
#0 2.955 Building dependency tree...
#0 2.957 Reading state information...
#0 2.962 E: Unable to locate package nodejs
------
failed to solve: executor failed running [/bin/sh -c apt-get install -y --force-yes nodejs]: exit code: 100
This ruby:2.1 image uses some old Debian 8 underhood and there is no nodejs
in the debian repositories. After quick googling I found that on nodesource
there is a script for adding nodejs
to the repository. Final Dockerfile
looks like this:
FROM ruby:2.1
WORKDIR /root
ADD Gemfile /root
ADD Gemfile.lock /root
RUN bundle install --without production
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN apt-get install -y --force-yes nodejs
RUN node --version
RUN npm --version
Now I can run the application on any computer just having docker and running docker compose up
. It works! The next step is populating the database, adding demo accounts, and hosting it on cloud as a showcase.
Key takeouts
For future projects I should:
- create a docker image for building/serving application
- document versions used
- os (e.g. Ubuntu 20.04)
- package managers, e.g. npm 2.1.3
- runtime versions, e.g. node 16.3.8