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:
rails new dummy_app
add capistrano
, capistrano-ext
, rvm-capistrano
(for RVM integration) and pg
gems(for Postgres database) to your Gemfile:
# Gemfile
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
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:
git init
And add to your .gitignore
file the following line:
/config/initializers/secret_token.rb
Database and staging environment config
Now, edit database configuration file (config/database.yml
), so it looks similar to this:
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:
/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:
cp config/environments/production.rb config/environments/staging.rb
Capistrano configuration
To create configuration files, run
capify .
It will create two files: config/deploy.rb
and Capfile
. Start with editing Capfile
and uncomment this line:
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
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
set :deploy_to, "/home/deploy/rails_projects/dummy_app"
and staging.rb
:
# config/deploy/production.rb
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:
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:
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:
cap deploy:setup
cap production deploy:setup
Then check if directory permissions, utilities and other dependencies are correct:
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:
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
.
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
):
sudo yum install monit
and edit the configuration file:
sudo vi /etc/monit.conf
Read carefully all the comments to get familiar with Monit. Then specify your configuration, e.g.:
# 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:
sudo monit reload
Or if you haven't started it yet:
sudo monit
To check status of processes being monitored, run:
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:
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):
/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</a>
- 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.