In this tutorial we'll see how to setup docker and docker-compose for local Ruby on Rails development.
TL;DR: if you don't want to read the whole story, we've created several gists for you. You just have to follow the instructions in their README:
This tutorial won't cover how to install Docker on your computer. You can refer to the official docker documentation.
We want to have a standard and homogeneous Ruby on Rails development environment isolated from all our other development projects.
This development environment must include webpack-dev-server as a test environment as well.
It should be possible to extend it with a database.
We want to run our local Ruby on Rails source code inside a docker container, whatever the version of Ruby or Rails.
We want a docker-compose setup to run our app container, webpack-dev-server, our tests suite and databases.
There are many benefits to use docker and docker-compose for local development environment setup.
For each of our projects we can specify in a very easy way:
Furthermore each of our projects will be completely isolated:
And we because we can cleary specify each versions of our stack we will be closer to dev/prod parity.
Finally we can completely streamline local dev environment setup: it's easier to onboard new developers on our projects or simply go back to a projects we haven't touched for months or years.
First things first we create the directory that will host our source code and create threee blank files Dockerfile
, docker-compose.yml
and entrypoint.sh
.
mkdir my-new-app
cd my-new-app
touch Dockerfile
touch docker-compose.yml
touch entrypoint.sh
The Dockerfile
is here to instruct Docker how to build a container from our source code and the docker-compose.yml
is here to instruct how to run multiple containers including our app container. We'll get to the file entrypoint.sh
later.
There are many possibilities to build a docker container image. The easiest and most flexible way is to write a Dockerfile
.
A Dockerfile
is a very simple text file which describes the different steps to follow to build a Docker image.
Each line of the Dockerfile is a step of the recipe beginning with a special keyword called command.
In our Dockerfile we'll use only 6 commands:
Here is the Dockerfile we'll use for all our Ruby on Rails projects. Don't be afraid we'll a do line by line analysis below!
FROM ruby:3.0.1
ARG USER_ID
ARG GROUP_ID
RUN addgroup --gid $GROUP_ID user
RUN adduser --disabled-password --gecos '' --uid $USER_ID --gid $GROUP_ID user
RUN apt-get update -qq
ENV GEM_HOME="/usr/src/app/bundle"
ENV PATH $GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH
RUN apt-get update -qq
# Install NodeJS
RUN apt-get install -y nodejs
# Install yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update -qq
RUN apt-get install -y yarn
WORKDIR /usr/src/app
# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
USER $USER_ID
ENTRYPOINT ["entrypoint.sh"]
We'll take the official Ruby docker image. As we want to use ruby 3.0.1 we'll base our docker image on the 3.0.1 docker version. Docker image tag version is the same as the ruby language version.
FROM ruby:3.0.1
As our goal is to install gems, we'll tell our docker container where to store our gems source code. The GEM_HOME
will instruct bundle where to store gems. Here we went gems source code to be installed in the bundle directory. We'll see below where this bundle dir will be really stored on our local filesystem.
ENV GEM_HOME="/usr/src/app/bundle"
And of course we want that gem binstubs are available in the standard PATH inside our container:
ENV PATH $GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH
Official ruby docker images are based on Ubuntu. We can use Ubuntu standard tooling. First we'd like to update the package information database. The -qq
option is here to output only error messages.
RUN apt-get update -qq
As Rails needs node
to be installed, we can install it with it's Ubuntu package.
RUN apt-get install -y nodejs
Recent rails versions install the webpacker
gem which needs yarn
to be installed. Here is a method to install yarn from the official Debian/Ubuntu repo provided by yarn.
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update
RUN apt-get install -y yarn
We want to specify that our base working directory will be /usr/src/app
.
WORKDIR /usr/src/app
Finally we add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
docker-compose is a tool to run multiple Docker containers in parallel.
A simple text file in YAML format called docker-compose.yml is needed to instruct docker-compose how to run muliple docker containers called services.
Here is the docker-compose.yml file we'll use. We'll begin with a very simple setup.
version: "2"
services:
web:
build: .
ports:
- 3000:3000
volumes:
- ./:/usr/src/app
command: bundle exec rails server -b 0.0.0.0 -p 3000 -e development
TBD
TBD
Now that we have setup our Docker, using the exact Ruby version we need for this project, it's time to run them!
First we'd like to run a bash command inside our app container to install Ruby on Rails.
docker-compose run web bash
We're now running a bash terminal inside our running app container.
We can now proceed with the installation of Rails.
gem install rails
The gem and all its dependencies will be installed in our local bundle
directory.
Once done we can create the structure of our app:
rails new .
Whenever you'd need to get out of a running docker container you can type exit
and type ENTER
or type CTRL+D
.
Launching the command docker-compose run web command
is our go to solution to run any command inside our running container. You can use it to run a bash terminal as described above, run a bundle command like docker-compose run web bundle install devise
or a yarn command like docker-compose run web yarn install @hotwired/turbo-rails
.
You can launch docker-compose up
and visit http//localhost:3000 in your browser.
Launching the command docker-compose up
is our go to solution to start any Rails projects using the docker and docker-compose setup used in this tutorial.
Voilà, we now have a running local Rails development environment using docker!
Recent version of Rails need webpack. The live asset compilation is done with a daemon called webpack-dev-server.
We'll add it as a new service in our docker-compose.yml file which looks like the following:
version: "2"
services:
web:
build: .
ports:
- 3000:3000
volumes:
- ./:/usr/src/app
links:
- webpack
command: bundle exec rails server -b 0.0.0.0 -p 3000 -e development
webpack:
build: .
ports:
- 3035:3035
volumes:
- ./:/usr/src/app
command: ./bin/webpack-dev-server
environment:
WEBPACK_HOST: "172.17.0.1:3035"
Furthermore as webpack-dev-server will run inside a container separated from our regular app container, we'll have to bind webpack-dev-server on 0.0.0.0
meaning on all network interfaces. That way it will be accessible from outside its container.
In config/webpacker.yml
replace the key development/dev_server/host
with the value 0.0.0.0
(localhost
is the default value and we don't want it).
Now stpo the stack, rebuild it and restart it.
docker-compose down
docker-compose build
docker-compose up
We now have a fleet of 2 containers running: one container running our Rails source code and one container running webpack-dev-server.
FROM ruby:3.0.1
RUN apt-get update
ENV GEM_HOME="/usr/src/app/bundle"
ENV PATH $GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH
RUN apt-get update -qq
RUN apt-get install -y postgresql-client
# Install NodeJS
# If you don't neet any specific NodeJS version use the Ubuntu package
RUN apt-get install -y nodejs
# If you'd like to install a specific NodeJS version, comment the line above and
# uncomment the lines below
# RUN apt-get install apt-transport-https
# ENV NODE_VERSION 14.16.1
# RUN cd /opt && \
# curl -L "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" | tar -xJf - && \
# mv -v node-v$NODE_VERSION-linux-x64 node
# ENV PATH /node_modules/.bin:/opt/node/bin:$PATH
# Install yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update -qq
RUN apt-get install -y yarn
WORKDIR /usr/src/app
# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
The first thing will be to add a new entry in the services
section of our docker-compose.yml
file which will now look lihe following:
version: "2"
services:
web:
build: .
volumes:
- .:/usr/src/app:rw
environment:
DATABASE_URL: postgresql://admin:admin-secret@postgresql:5432/my_new_app_development
WEBPACKER_DEV_SERVER_HOST: 172.17.0.1
links:
- postgresql
- webpack
ports:
- 3000:3000
command: bundle exec rails server -b 0.0.0.0 -p 3000 -e development
webpack:
build: .
ports:
- 3035:3035
volumes:
- ./:/usr/src/app
command: ./bin/webpack-dev-server
environment:
WEBPACK_HOST: "172.17.0.1:3035"
specs:
build: .
volumes:
- ./:/usr/src/app/
environment:
DATABASE_URL_TEST: postgresql://admin:admin-secret@postgresql:5432/my_new_app_test
RAILS_ENV: test
SMTP_URL: smtp://hello%40localhost@localhost
links:
- postgresql
command: "tail -F /dev/null"
postgresql:
image: scalingo/postgresql:12.2.0-6
ports:
- 5432:5432
environment:
DB_UID: 1000
volumes:
- ./docker/postgresql-data:/var/lib/postgresql:rw
command: /postgresql
At Scalingo (with our partners) we use trackers on our website.
Some of those are mandatory for the use of our website and can't be refused.
Some others are used to measure our audience as well as to improve our relationship with you or to send you quality content and advertising.