Running a Rails app with Webpacker and Docker

Dirk de Kok
6 min readSep 6, 2018

--

Rails is a great web framework built on top of the language Ruby, and I use it a lot for public web apps. The database I prefer is PostgreSQL, and I use Docker to build and package software with amazing results. A lot of new apps use React as Javascript framework.

Docker allows us to set up development environments in matter of minutes, and easily build a deployable image to run in production. In development you should use Docker for Mac with Docker Compose, in staging/production you can use Docker with Amazon ECS or Heroku. And yes Docker images can also be used with Kubernetes.

Modern Javascript uses a lot of libraries and processing frameworks, including NPM, Yarn and Webpack. So when you use React, you need all these tools. Rails has had the asset pipeline for a long time, and used Sprockets as the main tool. Since Rails 5.1 there is an alternative for Sprockets for Javascript, namely Webpacker. Definitely in Rails 6.0 Webpacker is the default JavaScript pipeline. Webpacker uses Webpack under the hood to compile all your Javascript files. One of the great things about Webpack is that in your development environment, it offers the option of live compilation of your Javascript files via webpack-dev-server. This means that you change a file on disk, it gets automatically recompiled and even pushed to the browser. This allows for very fast development. Of course in production you want to use the regular precompile step, combining all Javascript files into one minified Javascript file and not offer live recompilation.

So let’s get going and set up a dockerized environment with Rails (6), Webpacker and PostgreSQL. Check out the end result in this public Github repository: https://github.com/dirkdk/docker-rails-demo

Requirements

  1. Docker Compose like Docker for Mac
  2. Ruby version 2.6.3 or higher
  3. Rails version 6.0.0 or higher
  4. Webpacker 4 or higher
  5. PostgreSQL 11 or higher

Create Rails app

Run this command to create a barebones Rails app

rails new docker-rails-demo --skip-test --webpack --database=postgresql

This command creates an app with Webpacker and PostgreSQL. It skips the regular test framework because you should use Rspec instead. I’m leaving the Rspec installation out of this for now.

Add Docker files to the project

There are 2 files that we need. The first one is a single Dockerfile. This defines the main Docker image you will be using. In development we use this single image for 2 containers, web and webpacker. Web will serve the Rails code, while webpacker will serve the live compiled Javascript code. Think of Docker images as a class, and containers as different instances of that class.

The second file is docker-compose.yml. We define the various containers in our development environment in this file. It defines the networks and the links between the containers. Links involve network magic, including DNS lookups of the various containers you define, volumes to be used to mount directories.

Dockerfile

Our one custom Docker image is defined in the Dockerfile. Best to check the complete file out in the Github repo. This image starts with a Ruby base image defined on the first line with FROM ruby:2.6.3-slim. Then it runs a few commands via the RUN command to install software including the PostgreSQL client, Node and Yarn. Then installs Bundler and then Rails. Finally it copies the app code into the image.

Docker-compose.yml

Looking at the docker-compose.yml file, we use Compose file formats version 3.0. In our file we define multiple services. Services are also known as docker containers.

# docker-compose.yml 
version: "3.0"
services:

The first service is the database container called db using a standard image for Postgres. It sets the ports we expose and sets environment variable POSTGRES_PASSWORD.

# docker-compose.yml continued
db:
image: postgres:11
ports:
- 5432:5432
environment:
POSTGRES_PASSWORD: ${DB_USER_PASSWORD}

The second service is the webpacker container. We reference our custom image, set what command to run on start, define volumes and ports to expose

# docker-compose.yml continued
webpacker:
image: ${DOCKER_IMAGE_NAME-dockerrailsdemo}
command: ["./scripts/start_webpack_dev.sh"]
volumes:
- .:/opt/dockerrailsdemo:cached
ports:
- 3035:3035

Lastly, we define the web service that runs the image as the Rails container. We give it a build context, define the links it has to the other services, define ports, command to run and volumes to mount

# docker-compose.yml continued
web:
image: ${DOCKER_IMAGE_NAME-dockerrailsdemo}
build:
context: .
links:
- db
- webpacker
ports:
- 3000:3000
command: ["./scripts/wait-for-it.sh", "db:5432", "--", "./scripts/start_rails.sh"]
volumes:
- .:/opt/dockerrailsdemo:cached

The script wait-for-it.sh is needed because we want the database to be first available before we start the Rails app server as it depends on it.

Set environment variables

Before we can get going, we first need to create a .env file and set the variables DB_USER_PASSWORD and DB_HOST in it. This file will be shared between Docker and Rails. So for Rails to be able to read this file, we need to include gems for development and test.

# Gemfile
group :development, :test do
gem 'dotenv-rails'
end
# .env
DB_USER_PASSWORD=postgres
DB_HOST=db

Last configuration for Yarn and Webpacker

Rails does a lot of checkups on Yarn, a bit too much. Best is to disable this:

# config/environments/development.rb
config.webpacker.check_yarn_integrity = false

Also, we need to tell Rails that the webpacker server is running on the host webpacker and enable hot module reload. So set these values:

# config/webpacker.yml
development:
dev_server:
host: webpacker
hmr: true

Build Docker image

First step is to build the docker image, using the docker-compose.yml config file. We do this via the docker-compose command:

docker-compose build

This command will pull down the Ruby base image, run the Dockerfile and build the custom image with Rails with input from the docker-compose.yml file . The end output should be:

Successfully tagged dockerrailsdemo:latest

Create PostgreSQL database

For this run the following command on the web container:

docker-compose run web scripts/wait-for-it.sh db:5432 -- "rails db:create db:migrate"

This command uses the wait-for-it.sh script to wait till the database server is available to run the create and migrate commands.

In the example project I created a Product model with table, just to have a table in the database. With zero tables, running rails db:create db:migrate will not do anything. Feel free to remove the Product model, table, controllers, views and routes. You can do so by reverting commit 75e3c282953adcbce55a05dd4f36e331a4a2313a

First time run via docker-compose up, and javascript tag

Now you are ready to run the app for the first time:

docker-compose up

This command will boot up all the services alias containers. After a while, when you open http://localhost:3000 in your browser you will see the homepage.

One thing that is missing is that webpacker files are not loaded, so you need to change in your layouts/application.html.erb the line javascript_include_tag ‘application’ to javascript_pack_tag ‘application’, also stylesheet_include_tag to stylesheet_pack_tag. Change the contents of the console.log in app/javascript/packs/application.js, and see the browser do an automatic reload.

Asset pipeline in production

Using webpacker-server in development is great, but in production we want to precompile all assets and save them in the Docker image. Using logic inside a Dockerfile is not easy, so it is best to call a bash script with parameters. Most of the time what you will be doing is using docker-compose build in development, and doing a direct docker build for building your image for production. Our docker-compose.yml allows us to pass parameters, so we can use that to indicate that we don’t want the asset pipeline to be precompiled.

So in docker-compose.yml, we add these lines:

# docker-compose.yml continued
web:
image: ${DOCKER_IMAGE_NAME-dockerrailsdemo}
build:
context: .
args:
precompileassets: 'not'

And we add these lines to the Dockerfile:

After first line:ARG precompileassets

After last line:RUN scripts/potential_asset_precompile.sh $precompileassets

And this is the potential_asset_precompile.sh script:

# scripts/potential_asset_precompile.sh
#!/bin/bash

if [ $# -eq 0 ] || [ $1 != "not" ] ; then
echo 'precompiling assets'
RAILS_ENV=production rake assets:precompile
else
echo "argument is not, so not running precompile"
fi

So now you have a great start app to run Rails with Docker and Webpacker both in development and production. Clone the repo and go ahead. Send us a pull request if you have any suggestions to improve the demo app.

--

--