Deploy a Rails app to Docker with Capistrano

These instructions follow my previous post on deploying multiple Rails apps with Passenger, Nginx, and Docker. Go read (and do) all that first.

Assumptions

As always, this guide assumes the production server is running Ubuntu 14.04 and has all the requisite software already installed (e.g.: Docker, Rails, Capistrano, etc.). Further, it is assumed that you have a system similar to the one described here, and that by following the instruction provided, you have a Rails application deployed in a Docker container. I will be setting up Capistrano for the Rails app in that container.

Set up project in local development environment

Update the previous Docker configuration files

Nginx configuration

Change the existing docker/my-app.conf to look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
server_name example.com;
# This used to be /home/app/my-app/public;
root /home/app/my-app/current/public;
# Passenger
passenger_enabled on;
passenger_user app;
passenger_ruby /usr/bin/ruby2.2;
}

Change Dockerfile

Since Capistrano will be building the app, all those steps can be removed from the Dockerfile. It should now look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
FROM phusion/passenger-ruby22:latest
MAINTAINER Some Groovy Cat "hepcat@example.com"
# Set correct environment variables.
ENV HOME /root
ENV RAILS_ENV production
# Use baseimage-docker's init process.
CMD ["/sbin/my_init"]
# Start Nginx and Passenger
EXPOSE 80
RUN rm -f /etc/service/nginx/down
# Configure Nginx
RUN rm /etc/nginx/sites-enabled/default
ADD docker/my-app.conf /etc/nginx/sites-enabled/my-app.conf
ADD docker/postgres-env.conf /etc/nginx/main.d/postgres-env.conf
# Install the app
ADD . /home/app/my-app
WORKDIR /home/app/my-app
RUN chown -R app:app /home/app/my-app
RUN sudo -u app bundle install --deployment
RUN sudo -u app RAILS_ENV=production rake assets:precompile
# Clean up APT when done.
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

Set up Capistrano

You work on your Rails app locally and you deploy to production. From your local development environment, go to your app’s root directory and run:

1
2
cd my-app
cap install

If Capistrano is installed (gem install capistrano), you will see something similar to this:

1
2
3
4
5
6
7
mkdir -p config/deploy
create config/deploy.rb
create config/deploy/staging.rb
create config/deploy/production.rb
mkdir -p lib/capistrano/tasks
create Capfile
Capified

This produces a pre-cooked config/deploy.rb file. For the app deployed in the previous post, change it to look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
lock '3.4.0'
set :application, 'my-app'
set :repo_url, 'https://github/myprofile/my-app.git'
set :branch, 'master'
set :scm, :git
set :deploy_to, "/home/app/#{fetch(:application)}"
namespace :deploy do
desc 'Install node modules'
task :npm_install do
on roles(:app) do
execute "cd #{release_path} && npm install"
end
end
desc 'Build Docker images'
task :build do
on roles(:app) do
execute "cd #{release_path} && docker build -t #{fetch(:application)}-image ."
end
end
desc 'Restart application'
task :restart do
on roles(:app) do
execute "docker stop #{fetch(:application)} ; true"
execute "docker rm #{fetch(:application)} ; true"
execute "docker run --restart=always --name #{fetch(:application)} --expose 80 -e VIRTUAL_HOST=example.com --link postgres:postgres -d #{fetch(:application)}-image"
end
end
before :updated, 'deploy:npm_install'
after :publishing, 'deploy:build'
after :publishing, 'deploy:restart'
end

Then, in config/deploy/production.rb, modify as appropriate (it’s probably sufficient to tack this on to the end of the file):

1
server "example.com", user: "app", roles: %w{app web}

Commit and push your changes to your repository.

Set up project in production

Configuration

Before deploying with Capistrano, you need to do some configuration. Assuming that the app has already been cloned to the production machine these are the files that need adjusting:

  • my-app/config/database.yml
  • my-app/config/secrets.yml

The settings in here are not typically committed to the repository for security reasons. Assuming the Postgres configuration in the previous post, database.yml should look like this:

1
2
3
4
5
6
7
production:
<<: *default
database: my-app_production
username: postgres
password: secretp@ssword
host: <%= ENV['POSTGRES_PORT_5432_TCP_ADDR'] %>
port: <%= ENV['POSTGRES_PORT_5432_TCP_PORT'] %>

secrets.yml needs to have a secret key set for production. From your app’s home directory, run:

1
rake secret

Copy the key it produces and set it in secrets.yml:

1
2
production:
secret_key_base: PasteGeneratedKeyHere

Back in the local development environment…

From your app’s home directory:

1
cap production deploy

And now back to production…

If working with the app from the previous post, everything should be ready to go. If the site reports an error, however, you may need to setup the database in production. First, stop the Docker container:

1
docker stop my-app

Then, create and seed the database:

1
2
3
docker run --rm --link postgres:postgres my-app-image rake db:create
docker run --rm --link postgres:postgres my-app-image rake db:migrate
docker run --rm --link postgres:postgres my-app-image rake db:seed

And restart:

1
docker start my-app