Ruby on Rails: Using Capistrano to deploy application and run custom task

icelandcheng
7 min readMay 12, 2021

--

Sometimes we need to deploy our rails application to different stages(staging or production environment)which probably are remote machines, and we may need to do the deployment frequently, for each time the application has new version we would need to update the code to all stages environment. To save our time from manually doing repeated work in each time doing deployment, we could using Capistrano.

What is Capistrano?

Capistrano is a framework written in Ruby that provides automated deploy scripts. It supports the scripting and execution of arbitrary tasks, and includes a set of sane-default deployment workflows. Capistrano can be used to deploy web application to any number of machines simultaneously, in sequence or as a rolling set.

How to use it?

Put Capistrano in Gemfile

group :development do
gem "capistrano", "~> 3.10", require: false
gem "capistrano-rails", "~> 1.6", require: false
end

Install the gem and run the generator to create a basic set of configuration files

bundle install
bundle exec cap install

cap install will create below files and folders

$project_root/
|
|--Capfile
|
|--config/
| |
| |--deploy.rb
| |
| |--deploy/
| |
| |--production.rb
| |--staging.rb
|
|--lib/
|
|--capistrano/
|
|--tasks/

Capfile is a document used for importing the third party library would be used in deployment. Those third parties library already define many tasks like db migration, sidekiq setup. We could simply integration those task in our deployment through using these third party library.

config/deploy.rb is main configuration document of deployment. All the task we would like to do in deployment could be defined in this document.

config/deploy/*.rb are for stage configurations. Mostly it would include server ip and user login information used for the stage environment.

lib/capistrano/tasks is a folder for us to place the script file of our custom task. The task script file should be a rake file.

Getting start

First of all, make sure you could SSH from development system to deployment system, because Capistrano use SSH to deploy. Then, you could run command line bundle exec cap installto generate the files and folder we need in deployment. There are something we need to change in below files.

  • modify Capfile
require 'capistrano/setup'# Include default deployment tasks
require 'capistrano/deploy'
require 'capistrano/scm/git'
install_plugin Capistrano::SCM::Git
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano/puma'
install_plugin Capistrano::Puma
install_plugin Capistrano::Puma::Workers
require 'capistrano/sidekiq'
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
  • modify config/deploy.rb
set :application, 'my_app'      # my_app is your project name
set :repo_url, '<repo url>' # your project remote repository url
# it could be github project url
ask :branch, :master # Default deploy branch is :master
set :deploy_to, '/home/my_app' # deploy project to /home/my_app
  • modify config/deploy/staging.rb or config/deploy/production.rb and set the IP address or the domain name of the machine where your code will be deployed, the role of the machine. Capistrano uses a concept of a role to control over which tasks are run on which servers. For example, you may want to apply a task only to a database server and skip it when deploying to a web server. The role acts as a filter that tells Capistrano to group tasks with the same role together before executing them on a server matching a specific role.
server '10.0.3.193', user: 'deployer', roles: %w[db web]
server '10.0.3.22', user: 'deployer', roles: %w[app]
set :default_env, path: '/usr/local/ruby-2.5.3/bin:$PATH'set :puma_workers, 2
set :sidekiq_processes, 2

There are three common roles used in Capistrano tasks:

app role is used for tasks that runs on an application server, a server that generates dynamic content. In Rails this is puma server. If using capistrona-sidekiq, this role is the defualt role for capistrano to run sidekiq server.

db role is used for tasks that execute database server. For example, the deploy:migrate task for migrating the Rails database schema.

web role is used for tasks that deal with web servers that serve static content

we also could set custom role to specific machine. For example, if we would like to run a server only for redis. we could set a role call redis_server for a machine, and run custom task to start redis while deploy this specific machine. we would discuss how to run custom task in deployment later.

  • Start deployment by use capistrano command line. After finishing the modification of above configuration, we finally could start deployment. Here are some command line would be use to start the deployment. Usually we would use bundle exec cap 'environment' deploy to deploy our project to specific stage environment.
# list all available tasks
$ bundle exec cap -T

# deploy to the staging environment
$ bundle exec cap staging deploy

# deploy to the production environment
$ bundle exec cap production deploy

The deploy log would look like

After the deployment, the structure in your destination server would look like below

├── current -> /home/my_app_name/releases/20210511115200/
├── releases
│ ├── 20210509115200
│ └── 20210511115200
├── repo
│ └── <VCS related data>
├── revisions.log
└── shared
└── <linked_files and linked_dirs>

current is a symlink pointing to the latest release.

releases holds all deployments in a timestamped folder. These folders are the target of the current symlink.

repo holds the version control system configured. In case of a git repository the content will be a raw git repository (e.g. objects, refs, etc.).

revisions.log is used to log every deploy or rollback. Each entry is timestamped and the executing user.

shared contains the linked_files and linked_dirs which are symlinked into each release. This data persists across deployments and releases. It should be used for things like database configuration files and static and persistent user storage handed over from one release to the next.

Use custom role and run custom task

We could define custom task file in lib/capistrano/tasks and run it in deployment. For example, if we want to print Hello World in deployment, we could create a task to print Hello World and call it in deployment, the task script would like below script, and the on roles method could be integrated in the script to instruct Capistrano to run task only when login specific server.

namespace :greetings do
on roles(:app) do
execute "echo Hello Word"
end
end

We could print Hello World before deployment or after deployment. If print Hello World task need to run after deployment, the code below should be put in config/deploy.rb

after :deploy, 'greetings:hello'

After running bundle exec cap 'environment' deploy , we would see capistrano executing the task and showing Hello World in deploy log. We also would see that Hello World was printed only capistrano login to server 10.0.3.22

If you want greetings.rake running before deployment, then below code should be put in config/deploy.rb

before :deploy, 'greetings:hello'

The deploy log print the Hello World at the start of the deployment

Besides setting default roles(app, web, db) to specific machine, we could define custom role for running custom task. For example, If we need one of a staging server only run for sidekiq, we could set a sidekiq_server role for the specific machine, and start sidekiq would while capistrano login the machine during deployment. The custom role should set in config/deploy/staging.rb

server '10.0.3.193', user: 'deployer', roles: %w[db web]
server '10.0.3.22', user: 'deployer', roles: %w[app]
server '10.0.3.20', user: 'deployer', roles: %w[sidekiq_server]
set :default_env, path: '/usr/local/ruby-2.5.3/bin:$PATH'set :puma_workers, 2
set :sidekiq_processes, 2

The custom task script is should set as below. The script indicates that sidekiq should be run when the login machine role is sidekiq_server.

namespace :sidekiq_server do
task :start do
on roles(:sidekiq_server) do
within current_path do
execute :bundle, "exec sidekiq --environment staging --logfile /home/my_app/shared/log/sidekiq.log --daemon"
end
end
end
end

If we want the task run after deployment, we could put below code in config/deploy.rb

after :deploy, 'sidekiq_server:start'

Finally, we run bundle exec cap staging deploy , and we could see the task run only login to sidekiq_server machine(10.0.3.20)

When we open the sidekiq web page, we could find the machine 10.0.3.20 running sidekiq queque.

Reference

--

--

icelandcheng

Programming Skill learner and Sharer | Ruby on Rails | Golang | Vue.js | Web Map API