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
mkdir my-new-app cd my-new-app touch Dockerfile touch docker-compose.yml touch entrypoint.sh
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
There are many possibilities to build a docker container image. The easiest and most flexible way is to write 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.
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.
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
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
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
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
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
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.
config/webpacker.yml replace the key
development/dev_server/host with the value
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