This tutorial is an extraction from a talk I recently presented about docker and rails apps at the Barcelona on Rails user group. I’ll explain how to integrate docker into an existing rails app workflow.
Dockerizing our dependencies
The first step to integrate docker into our workflow is to start with our application dependencies.
As an example I’ll use the open source app fulcrum. I assume this application is running locally as would be the case with an existing rails app. For this example we’ll use the postgresql database.
So the first thing is to initialize our database container. For that we execute the following command.
docker run -d --name fulcrum-postgres \
-e POSTGRES_USER=fulcrum -e POSTGRES_PASSWORD=fulcrum \
-p 5432:5432 postgres
So we’re creating a container and run it as a daemon (-d), we setup some environment variables (-e) for the username and password and make the ports available (-p HOSTPORT:CONTAINERPORT).
Now we need to adjust our config/database.yml, it should look something like this:
development:
adapter: postgresql
encoding: unicode
database: fulcrum_development
pool: 5
host: <%= ENV['POSTGRES_HOST'] %>
username: <%= ENV['POSTGRES_USER'] %>
password: <%= ENV['POSTGRES_PASSWORD'] %>
test:
adapter: postgresql
encoding: unicode
database: fulcrum_test
pool: 5
host: <%= ENV['POSTGRES_HOST'] %>
username: <%= ENV['POSTGRES_USER'] %>
password: <%= ENV['POSTGRES_PASSWORD'] %>
And now we run our rails app
# Initialize the db
POSTGRES_HOST=`boot2docker ip` \
POSTGRES_USER=fulcrum \
POSTGRES_PASSWORD=fulcrum \
bundle exec rake db:setup
# Run the app
POSTGRES_HOST=`boot2docker ip` \
POSTGRES_USER=fulcrum \
POSTGRES_PASSWORD=fulcrum \
bundle exec rails s
Please note that for the value for the POSTGRES_HOST I’m using boot2docker, if you’re running on linux or against another docker host replace this accordingly.
Now when we visit localhost:3000 our app will be running against our database in a docker container.
For a more complicated app, we’d do the same for the rest of our dependencies. By starting with the dependencies, we are preparing the road so we can have a more decoupled environment while maintaining our regular workflow.
Dockerizing the app
The next step is to run the application itself inside a container. For this we’ll need to create our Dockerfile.
There are many different places to start from. We’ll use the official ruby 2.1.2 container since this is the ruby version our app needs, but defining our own from scratch is quite easy as well.
Our Dockerfile should look like this
FROM ruby:2.1.5
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y \
nodejs \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY Gemfile* /app/
RUN bundle install -j4
ADD . /app
# Let's create a user to run the app that is not root
RUN usr/sbin/useradd --create-home --home-dir /app --shell /bin/bash fulcrum
RUN chown -R fulcrum:fulcrum /app
USER fulcrum
CMD ["rails", "server"]
We also need to tell docker certain files we don’t want as part of our container, we create a .dockerignore file like this:
.git
.bundle
vendor/bundle
After creating the files in the root of our app we execute:
docker build -t fulcrum-web .
Once that is finised we can run our app like this:
docker run --rm -p 3000:3000 --link fulcrum-postgres:postgres \
-e POSTGRES_HOST=postgres \
-e POSTGRES_USERNAME=fulcrum \
-e POSTGRES_PASSWORD=fulcrum \
fulcrum-web
So what is this doing exactly? We’re linking our database container to our web container (–link), we’re exposing the port 3000 (-p) and we’re also setting environment variables (-e) with the values we used when creating our db container. After executing this we can go visit our app running on http://DOCKER_HOST:3000
The first thing we can see is that we have to set many duplicate environment variables from the database when running our container, fortunately docker allows us to have access to a linked container environment variables.
We can change now our config/database.yml replacing the username and password like this:
username: <%= ENV['POSTGRES_USER'] || ENV['POSTGRES_ENV_POSTGRES_USER'] %>
password: <%= ENV['POSTGRES_PASSWORD'] || ENV['POSTGRES_ENV_POSTGRES_PASSWORD'] %>
We rebuild our container again, and now we can start it simply like:
docker run --rm -p 3000:3000 --link fulcrum-postgres:postgres \
-e POSTGRES_HOST=postgres \
fulcrum-web
Now, recreating our container every time we make a file change is going the get very annoying quite soon. For this we can also leverage another docker facility called volumes. This will allow our container to have access to our local files. To do this simply we start our container like this:
docker run --rm -p 3000:3000 --link fulcrum-postgres:postgres \
-e POSTGRES_HOST=postgres \
-v $PWD:/app \
fulcrum-web
Note: Please make sure you delete your .bundle/config local file to avoid issues with bundler inside the container.
Automating our setup with docker compose
At this point we have our app running successfully inside a docker container, but having to be typing this commands constantly is not fun.
To finish our setup we’ll use docker compose.
So let’s create our docker-compose.yml.
web:
build: .
links:
- db:postgres
environment:
POSTGRES_HOST: postgres
ports:
- "3000:3000"
volumes:
- .:/app
db:
image: postgres
environment:
POSTGRES_USER: fulcrum
POSTGRES_PASSWORD: fulcrum
Now to run our app we simply do:
docker-compose up
The first time we access our app it will complain our db is not created we can simply fix this by running:
docker-compose run web rake db:setup
And last but not least if you want to run your tests then you can simply do:
docker-compose run web rake db:test:prepare spec
The end
So that’s it, I hope this tutorial helps you get started with docker with your existing rails applications. If you want more info about docker compose, check out my article about fig (docker compose’s predecesor) here