How to Boot a New Rails Project With Docker and Heroku

A few years ago, I used Heroku to deploy my side-project. It provides great service, but I remember that updates to the Heroku Stack was a nightmare … Versions of the OS (and nearly everything) changed. The migration was a matter of days, and while doing a side-project, this was difficult. At the time, I remember thinking that using branches and VMs would have been the solution.

Now that I started to use Heroku again, I decided to use Docker from the beginning. More specifically, I am expecting :

  • to have a minimal setup on my host machine
  • to use the same infrastructure in dev than in production
  • to simplify switching to a new machine
  • to simplify the migration to the next Heroku stack

As an added benefit, if ever someone else joins me in my side-project, it will be a matter of minutes before we can all work on the same infrastructure !

Heroku provides a tutorial about how to deploy an existing Rails app to heroku using containers. Unfortunately, I did yet have an existing rails app … So the first challenge I faced, was how to create a Rails app without actually installing Rails on my machine. The trick is to bootstrap rails in docker itself before packaging all this for Heroku.

1. Install the required software

I installed only 4 things on my host machine – Docker instructions – Docker Compose instructions – Heroku Toolbelt instructions – Heroku container plugin heroku plugins:install heroku-container-tools

That’s all I changed to my host machine.

2. Setup docker

First, let’s create a new dir and step into it. Run :

1
2
mkdir docker-rails-heroku
cd docker-rails-heroku

To prepare the Heroku setup, create a Procfile

1
web: bundle exec puma -C config/puma.rb

and app.json

1
2
3
4
5
6
7
8
{
  "name": "Docker Rails Heroku",
  "description": "An example app.json for container-deploy",
  "image": "heroku/ruby",
  "addons": [
    "heroku-postgresql"
  ]
}

To generate docker files for Heroku, run :

1
heroku container:init

You want to run Rails in dev mode locally, so we need to override Heroku’s default env (Check my previous post for details)

Create an .env file

1
RAILS_ENV=development

and docker-compose.override.yml

1
2
3
4
5
6
7
8
9
web:
  volumes:
    - '.:/app/user'
  environment:
    RAILS_ENV: "${RAILS_ENV}"

shell:
  environment:
    RAILS_ENV: "${RAILS_ENV}"

3. Create the Rails app

It’s now time to follow the official docker-compose rails tutorial to bootstrap the rails app and directories :

Change Dockerfile to

1
2
3
4
5
6
7
8
9
10
# FROM heroku/ruby

FROM ruby:2.2.0
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
ADD . /myapp

Create a bootstrap Gemfile with the content

1
2
source 'https://rubygems.org'
gem 'rails', '4.2.0'

Bundle install within the container requires a existing Gemfile.lock

1
2
# Create an empty Gemfile.lock
touch Gemfile.lock

It’s now time to build your docker container to be able to run rails and generate your source files. Run the following :

1
2
3
4
5
# Build your containers
docker-compose build

# Run rails within the shell container and generate rails files
docker-compose run shell bundle exec rails new . --force --database=postgresql --skip-bundle

Unfortunately, rails is ran as root inside the container. We can change ownership and rights with this command :

1
2
3
4
5
# Change ownership
sudo chown -R $USER:$USER .

# Change rights
sudo chmod -R ug+rw .

4. Make it Heroku ready

Now that the rails files are generated, It’s time to replace the bootstrap settings with real Heroku Dockerfile

Revert Dockerfile to simply :

1
FROM heroku/ruby

Heroku uses Puma so we need to add it to our Gemfile

1
2
# Use Puma as the app server
gem 'puma', '~> 3.0'

We also need to add a config file for Puma. Create config/puma.rb with this content (you can check heroku doc for details)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['RAILS_MAX_THREADS'] || 5)
threads threads_count, threads_count

preload_app!

rackup      DefaultRackup
port        ENV['PORT']     || 3000
environment ENV['RACK_ENV'] || 'development'

on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
  ActiveRecord::Base.establish_connection
end

It should now be possible to rebuild the container, and run the app :

1
2
3
4
5
# Rebuild the containers
docker-compose build

# Start the rails app using the web container
docker-compose up web

The app should be accessible at http://0.0.0.0:8080

5. Deploying to heroku

We’re almost ready to deploy to heroku.

First, we need to exclude development files from our image. For this, we need to create a .dockerignore file with the content

1
2
3
4
5
6
7
8
9
10
.git*
db/*.sqlite3
db/*.sqlite3-journal
log/*
tmp/*
Dockerfile
.env
docker-compose.yml
docker-compose.override.yml
README.rdoc

It’s then classic Heroku deploy commands :

1
2
3
4
5
# create an Heroku app
heroku apps:create <your-app-name>

# And deploy to it
heroku container:release --app <your-app-name>

Your app should be accessible on line at https://<your-app-name>.herokuapp.com/

Rails does not provide a default homepage in production. But you can check the logs with

1
heroku logs --app <your-app-name>

6. Running commands

When in development mode, you might want to run rails or other commands on your source code again. The shell container exists just for that, run docker-compose run shell ....

1
2
# For example, to update your bundle
docker-compose run shell bundle update

EDIT 2016-07-20

For the moment, there’s a catch with bundle install or update commands, as the gems are installed outside the shared volume, only Gemfile.lock will be updated, which required to run docker-compose build again … I’ll have a look into this later and see if I can fix that.

1
2
docker-compose run shell bundle update
docker-compose build

Comments