Useful Docker Compose with Rails
Let’s dive into the practical side of docker compose and what you can do when you run docker-compose with your rails application. I’ve assumed you have already made a DockerFile. If not a sample is included at the bottom of the article.
To be overly brief, docker-compose will cluster a series of containers together for you and take care of all the networking. It allows you to communicate among containers defined in your compose file by name. Here is the docker-compose file we will reference throughout:
version: '3'services: app: build: . entrypoint: /bin/sh command: rails s -b 0.0.0.0 -p 3000 ports: - 3000:3000 volumes: - .:/app env_file: .env environment: - DEV_DATABASE_HOST=db - DEV_MAIL_HOST=mailcatcher depends_on: - db stdin_open: true tty: true db: image: postgres:latest ports: - 5432:5432 environment: - POSTGRES_DB=database-dev - POSTGRES_USER=root delayed_job: build: . command: rake jobs:work volumes: - .:/app env_file: .env environment: - DEV_DATABASE_HOST=db - DEV_MAIL_HOST=mailcatcher depends_on: - db stdin_open: true tty: true mailcatcher: image: zolweb/docker-mailcatcher:latest ports: - "1025:1025" - "1080:1080"
There is a lot going on here. We will actually be creating four containers, each with a unique purpose and allowing them all to communicate with each other in the cluster (the networking is handled by docker-compose).
First is the application itself, app
. It is going to run the rails app on port 3000, and the port mapping of 3000:3000
is how you will be able to access it. If you run the application you can reach it at localhost:3000
because your 3000 is mapped to the container’s 3000.
The env variables will be loaded from .env
and it will have a volume for it’s filesystem. DEV_DATABASE_HOST
will be the db container and DEV_MAIL_HOST
will be the mailcatcher container that comes up later in the docker-compose up
command. The app
depends on the db
and won’t come up until the db
is ready.
The delayed_job
container is essentially the same thing, but it will run the worker instead of the application. You can tell this from the cmd
or command line.
The database container is very easy to get up and going. You don’t need to have an image for it because it is part of the docker registry. Simply defining the image as postgres:latest
means that the image will be downloaded for the container with the most recent version of postgres. If you want a versioned postgres use the version instead of latest
. It will run your postgres on 5432, the default postgres port. Once it is up and running you can connect to it the same way that you would the app
container.
Mailcatcher is much the same way. If you are not familiar with mailcatcher go read up on it. But it is a simple application for monitoring and intercepting outgoing mail from your application. It even has a simple web based client so you can see what your outbound emails look like. The compose file will download and build the container for you and the 1080 and 1025 ports are mapped for you in docker compose file. These are required by mailcatcher.
Now for the magic. docker-compose up --build -d
This will look at your compose file and build the containers (you don’t need to build each time you run them). It will also run them in daemon mode so you can attach to them at later times of your choosing. You may have wondered about the stdin_open
and tty
lines in the compose file. They will allow you to attach to a container and debug (byebug). To get your list of container names: docker ps
To attach, pick the name of a container and docker attach #{container name}
You will see logs as they come through.
You can also perform tasks on a given container by using docker-compose
and providing the container name as defined in the docker compose. docker-compose exec #{docker-compose container name} #{command you want to execute}
Some popular ones would probably be:
docker-compose exec app rake db:migrate
docker-compose exec app rails c
You can pick any of your containers and run relevant commands in them such as psql
using the exec
which is short hand for execute.
To stop and start your containers:
docker start/stop #{container as found by docker ps}
Otherwise you can use docker-compose to control the whole cluster:
docker-compose up/down
Whenever you make certain changes, like gem files or ruby version it’s normally a good idea to docker-compose up --build
to rebuild your containers.
As promised here is a sample DockerFile for a rails application:
# Pick the ruby version for your rails app. This one is a rails 4 app and using ruby 2.2.3. Consider using 2.5.0 at least.
FROM ruby:2.2.3# Installing some needed things here. Including ghostscript because this rails app works with pdfs. You may consider making adjustments.
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs && apt-get install -y ghostscript
# Make the directory for the app
RUN mkdir /app# Set the working directory of everything to the directory we just made.
WORKDIR /app# Copy the gemfile and gemfile.lock so we can run bundle on it
COPY Gemfile GemfileCOPY Gemfile.lock Gemfile.lock# Install and run bundle to get the app ready
RUN gem install bundlerRUN bundle install# Copy the Rails application into placeCOPY . .# Expose port 3000 on the container
EXPOSE 3000# Run the application on port 3000
CMD rails s -b 0.0.0.0 -p 3000
Happy containering!