Deploying a legacy Rails app to Heroku, via Docker

Recently, we worked on deploying a legacy Rails app to Heroku. The minimum Ruby version that Heroku supports is 2.7.6. Apps running older versions must be upgraded before they can be deployed. Thankfully, this problem can be solved by deploying the app as a container. This post explains how to containerize an application and deploy it to Heroku.

Dockerize the application

First, we need to create a simple Dockerfile for the application:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FROM ruby:2.4.2

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

RUN mkdir /myapp
WORKDIR /myapp

COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock

RUN bundle install

COPY . /myapp

EXPOSE 3000
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]

Different Ruby apps will have different Dockerfiles. This example was built for a simple Rails 4 app running Ruby 2.4.2 but it can be used as a starting point and modified for different use cases.

With this Dockerfile, the app can be run locally:

1
docker build -t myapp . && docker run -it myapp

There may be errors, when running the app. This is common when containerizing a legacy app, there are often dependencies which need to be fixed or upgraded.

Adding Heroku.yaml

Once the app is running locally, we need to add a heroku.yaml file:

1
2
3
4
5
6
7
build:
  docker:
    web: Dockerfile
  config:
    RAILS_ENV: production
run:
  web: bundle exec rails server -b 0.0.0.0 -p $PORT

This tells Heroku to build an image using the Dockerfile and then specifies a process for running the container.

Notice that the web process is running on a port specified by $PORT. This is required because Heroku dynamically assigns a port, on each deploy, which the web process must listen on.

Building an app and deploying

At this point we can build a new Heroku app, either via the UI or CLI. By default Heroku apps do not run container builds. We must change the stack via the Heroku CLI:

1
heroku stack:set container

The codebase and Heroku app are now ready, we can push to Heroku to start the build and deployment.

Switch over DB and copy over environment variables

The app has been deployed successfully and is running on Heroku but we’re missing important components such as a database. There are two options here:

  1. Migrate addons from the legacy Heroku app to the new app
  2. Copy Config Vars from the legacy app to the new app, reusing the legacy app’s add-ons

Option one is cleaner and would allow us to completely decommission the old application. Option two is faster.

We recommend starting with option 2 in order to verify that the new app is working correctly. Then gradually migrate add-ons across.