Karol Galanciak - Ruby on Rails and Ember.js consultant

Rails Applications Deployment With Capistrano and After Deployment Setup

You’ve just setup your production server, but still haven’t deployed your app? Then this the right place for you. You are going to learn how to deploy your app to remote server, deal with some config files, create staging environment and setup monitoring tools.

Introducing Capistrano

Capistrano is an excellent tool to automate your deployment process using Rake DSL. It allows you to deploy applications using various source control management systems like Git, run migrations on remote server, restart application and many more.

Application setup

Let’s start with creating Rails application:

1
rails new dummy_app

add capistrano, capistrano-ext, rvm-capistrano (for RVM integration) and pg gems(for Postgres database) to your Gemfile:

Gemfile
1
2
3
4
gem 'pg'
gem 'capistrano'
gem 'rvm-capistrano'
gem 'capistrano-ext'

NOTE: When writing this article, the current Capistrano version was 2.15.5 which I believe is buggy as I had some problems with authentication while deploying. If you have the same problem, use 2.15.3 version instead.

You should also uncomment this line:

Gemfile
1
gem 'therubyracer', platforms: :ruby

And run bundle install.

Is my app secure?

Well, it depends. If you are the only person working on it, then you are safe. But if you aren’t, then your session secret is available to other people, which makes your app vulnerable to carefully crafted attacks and you should exclude it from your code repository. Initialize git repository in your application:

1
git init

And add to your .gitignore file the following line:

.gitignore
1
/config/initializers/secret_token.rb

Database and staging environment config

Now, edit database configuration file (config/database.yml), so it looks similar to this:

config/database.yml
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
development:
  adapter: postgresql
  host: localhost
  database: dummy_app_test
  username: dummy_app_user
  password: my-secret-password

test:
  adapter: postgresql
  host: localhost
  database: dummy_app_test
  username: dummy_app_user
  password: my-secret-password

production:
  adapter: postgresql
  host: localhost
  database: dummy_app_production
  username: dummy_app_user
  password: my-secret-password

staging:
  adapter: postgresql
  host: localhost
  database: dummy_app_production
  username: dummy_app_user
  password: my-secret-password

There is one extra thing in this config: the staging environment. We are going to use pre-production environment for testing purposes. If you want to share the same database between production and staging, then leave this config as it is.

You should also exclude database.yml from your code repository, not only for keeping your passwords secret, but also to prevent overriding local configuration when fetching code from repository - add to .gitignore file:

1
/config/database.yml

Staging environment should be close to production as much as possible, so copy the production.rb file and rename it to staging.rb:

1
cp config/environments/production.rb config/environments/staging.rb

Capistrano configuration

To create configuration files, run

1
capify .

It will create two files: config/deploy.rb and Capfile. Start with editing Capfile and uncomment this line:

Capfile
1
load 'deploy/assets'

Next, open the deploy.rb remove the default content, copy & paste the script below and adjust it according to the comments.

config/deploy.rb
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
require "bundler/capistrano"
require 'capistrano/ext/multistage'
require "rvm/capistrano"

# General

set :keep_releases, 5 # or any other number of releases you would like to keep
ssh_options[:port] = 12345 # if you haven't changed anything in SSH config, set it to 22
ssh_options[:forward_agent] = true # forward ssh keys
default_run_options[:pty] = true # set for the password prommpt

set :application, "dummy_app" # set the name of you application here
set :user, "deploy" # and the server user name

set :stages, ["staging", "production"] # Set staging and production environment
set :default_stage, "staging" # Use staging environment as the default one to prevent accidentally deploying to production


set :deploy_via, :remote_cache # it will only fetch from the repository on server, not clone the entire repository from scratch

set :use_sudo, false # do not use sudo

# Git

set :scm, :git # set git as a Source Code Manager
set :repository,  "ssh://deploy@your.ip.goes.here:port/home/#{user}/repos/#{application}.git" # point your repository here

set :branch, "master" # set git branch here

# Server

role :web, "server.ip.goes.here" # HTTP Server
role :app, "server.ip.goes.here" # server with your app
role :db,  "server.ip.goes.here", :primary => true # database server
role :db,  "server.ip.goes.here"

# Passenger

namespace :deploy do
 task :start do ; end
 task :stop do ; end
 task :restart, :roles => :app do # restart your app after finalizing deployment
   run "touch #{current_path}/tmp/restart.txt"
 end
end

# Symlinking

namespace :deploy do
  task :symlink_db, :roles => :app do
    run "ln -nfs #{deploy_to}/shared/config/database.yml #{release_path}/config/database.yml" # This file is not included repository, so we will create a symlink 
  end
  task :symlink_secret_token, :roles => :app do
    run "ln -nfs #{deploy_to}/shared/config/initializers/secret_token.rb #{release_path}/config/initializers/secret_token.rb" # This file is not included repository, so we will create a symlink 
  end
end

before 'deploy:assets:precompile', 'deploy:symlink_db' # callback: run this task before deploy:assets:precompile
before 'deploy:assets:precompile', 'deploy:symlink_secret_token' # # callback: run this task before deploy:assets:precompile
after "deploy", "deploy:cleanup" # delete old releases

Now, create deploy directory in config directory and add production.rb and staging.rb files there. You have to specify paths, where the production and staging app instance will be deployed. Let’s edit the production.rb file:

config/deploy/production.rb
1
set :deploy_to, "/home/deploy/rails_projects/dummy_app"

and staging.rb:

config/deploy/production.rb
1
set :deploy_to, "/home/deploy/rails_projects/dummy_app_staging"

That’s a basic configuration that should be sufficient in most cases. But Capistrano is a really sophisticated tool, you can specify diffrent servers for staging and production environment, diffrent git branches, diffrent repositories and many more. Just specify different settings in production.rb and staging.rb if you need to.

Deployment

Before deploying your app, you have to setup git repository. We will create just an empty repo in /home/deploy/repos directory on remote server:

1
ssh your-server "mkdir /home/deploy/repos && mkdir /home/deploy/repos/dummy_app.git  && git init --bare /home/deploy/repos/dummy_app.git"

Keeping the Git repositories on deploy user might not be the best idea, especially when you want to give other people access to the repo, but it sufficient for demonstration purposes. In other cases, you should rather create seperate git user with a limited shell (git-shell) or use some sophisticated tools like Gitolite if you need to.

Now we can commit all changes and deploy our application:

1
2
3
4
git add --all
git commit -am "Setup deployment configuration"
git remote add origin ssh://deploy@your.ip.goes.here:port/home/deploy/repos/dummy_app.git
git push origin master

You are ready to deploy our application, but before that you need to setup databases and http server configuration. If you have any problem, check this one out (remember about specifying appropriate rails_env). Firstly, let the Capistrano deal with creating all the neccessary directories in both staging and production environments:

1
2
cap deploy:setup
cap production deploy:setup

Then check if directory permissions, utilities and other dependencies are correct:

1
2
cap deploy:check
cap production deploy:check

In both cases you should have output ending with: You appear to have all necessary dependencies installed.

The last thing before deployment: we haven’t included secret_token.rb and database.yml files in repo, so we have to copy them on remote server:

1
2
3
4
5
scp config/database.yml you-server:/home/deploy && scp config/initializers/secret_token.rb your-server:/home/deploy
ssh your_server "mkdir /home/deploy/rails_projects/dummy_app/shared/config && mkdir /home/deploy/rails_projects/dummy_app_staging/shared/config"
ssh your_server "mkdir /home/deploy/rails_projects/dummy_app/shared/config/initializers && mkdir /home/deploy/rails_projects/dummy_app_staging/shared/config/initializers"
ssh you_server "cp /home/deploy/database.yml /home/deploy/rails_projects/dummy_app/shared/config/database.yml && mv /home/deploy/database.yml /home/deploy/rails_projects/dummy_app_staging/shared/config/database.yml"
ssh you_server "cp /home/deploy/secret_token.rb /home/deploy/rails_projects/dummy_app/shared/config/initializers/secret_token.rb && mv /home/deploy/secret_token.rb /home/deploy/rails_projects/dummy_app_staging/shared/config/initializers/secret_token.rb"

And you can deploy your application. Instead of cap deploy, use cap deploy:cold and cap production deploy:cold - it will deploy the app, run all migrations and run deploy start instead of cap:restart.

1
2
cap deploy:cold
cap production deploy:cold

Done! You have just deployed your application. Next time use cap deploy or cap deploy:migrations to run migrations.

Monitoring with Monit

How do you know if everything is running correctly after deployment? Well, you don’t know, unless you install a monitoring tool. Monit is a great and easy to configure utility for managing and monitoring processes. Let’s start with installing Monit on remote server (I use CentOS Linux, there are some differences between distros, so the location of the files might be diffrent, e.g. on Debian, the configuration file is in /etc/monit/monit.rc):

1
sudo yum install monit

and edit the configuration file:

1
sudo vi /etc/monit.conf

Read carefully all the comments to get familiar with Monit. Then specify your configuration, e.g.:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# check services every minute
set daemon 60


# monitor nginx
check process nginx with pidfile /opt/nginx/logs/nginx.pid
  start program = "/etc/init.d/nginx start"
  stop program  = "/etc/init.d/nginx stop"

# monitor postgres
check process postgres with pidfile /var/lib/pgsql/9.2/data/postmaster.pid
  start program = "/etc/init.d/postgresql start"
  stop  program = "/etc/init.d/postgresql stop"

# web interface setup
set httpd port 2812 and
  use address localhost
  allow username:"password" # specify username and password for http basic authentication
  allow localhost
  allow @monit

There are a lot of available Monit recipies, e.g. here, so it is quite easy to setup. When you finish, restart Monit:

1
sudo monit reload

Or if you haven’t started it yet:

1
sudo monit

To check status of processes being monitored, run:

1
sudo monit status

You don’t have to ssh on your server everytime you want to check the status, Monit comes with a very nice web interface. Here is a simple Nginx configuration, so that you will be able to access Monit via your-ip:1111/monit address:

1
2
3
4
5
6
7
8
9
10
server {
  listen 1111;
  server_name localhost;

  location /monit/ {
    proxy_pass http://127.0.0.1:2812/; # pass query to backend, replace /monit/ uri part to just /
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

Use password and username specified in Monit configuration.

Logrotate

After some time your Rails app logs and especially Nginx logs might be really large, so it is a good idea to somehow manage them. Fortunately, you can use system-built utility called Logrotate. Just open /etc/logrotate.conf and paste the configuration below (remember about changing path to your application):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/home/deploy/rails_projects/your_app_name/shared/log/*.log {
  daily
  rotate 30
  missingok
  compress
  notifempty
  delaycompress
  sharedscripts
  copytruncate
}


/opt/nginx/logs/*.log {
  daily
  rotate 30
  compress
  missingok
  notifempty
  delaycompress
  sharedscripts

  postrotate
    [ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run/nginx.pid`
  endscript }

Here is the options explanation:

  • daily - rotate the logs every day
  • rotate 30 - rotate logs 30 times, after that delete the oldest
  • missingok - ignore if the file doesn’t exist
  • compress - compress logs with gzip
  • notifempty - leave file if the logs are empty
  • delaycompress - postpone compression of the file to the next cycle
  • sharedscripts - tell only once that the logs have been rotated, not several times for every group
  • copytruncate - copy the log file and and truncate the original one
  • postrotate [ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run/nginx.pid` endscript - tell Nginx that the logs have been rotated and use the new ones.

And that’s is it! Cron by default runs logrotate every day.

Comments