Example of deploying Rails application to a Linux server with Nginx+Passenger.
Find all necessary files in the repository and read about features below:
Gemfile
group :development do
gem 'capistrano', '~> 3.1'
gem 'capistrano-rails', '~> 1.1'
...
end
Run the command
cap install
This command will create files:
Capfile
config/deploy.rb
config/deploy/production.rb
config/deploy/staging.rb
lib/capistrano/tasks # directory
Gemfile
group :development do
gem 'capistrano', '~> 3.1'
gem 'capistrano-rails', '~> 1.1'
#gem 'capistrano-bundler', '~> 1.1'
gem 'capistrano-rvm', '~> 0.1'
end
Capfile
require 'capistrano/rvm'
# require 'capistrano/rbenv'
# require 'capistrano/chruby'
#require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
Restart application after deploy:
namespace :deploy do
desc 'Restart application'
task :restart do
on roles(:app), in: :sequence, wait: 5 do
execute :touch, release_path.join('tmp/restart.txt')
end
end
after :publishing, :restart
end
You can run this command manually to restart the app
cap production deploy:restart
This gem provides more functionality on working with Passenger: https://github.com/capistrano/passenger
Some of the files should be stored in shared folder and shared across all releases. File from the 'linked_files' list are symlinked to files in shared folder.
For example, you may want to store users' uploaded files in public/uploads directory. This directory should not new in each release after deploy.
set :linked_dirs, fetch(:linked_dirs, []).push('bin', 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')
set :linked_dirs, fetch(:linked_dirs) + %w{public/uploads}
set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')
Note! You need to copy linked files to the server before deploy manually. The files are not copied to the shared folder by Capistrano.
Task to copy files to shared directory:
set :config_dirs, %W{config config/environments/#{fetch(:stage)} public/uploads}
set :config_files, %w{config/database.yml config/secrets.yml}
namespace :deploy do
desc 'Copy files from application to shared directory'
## copy the files to the shared directories
task :copy_config_files do
on roles(:app) do
# create dirs
fetch(:config_dirs).each do |dirname|
path = File.join shared_path, dirname
execute "mkdir -p #{path}"
end
# copy config files
fetch(:config_files).each do |filename|
remote_path = File.join shared_path, filename
upload! filename, remote_path
end
end
end
end
run the command:
cap production deploy:copy_config_files
By default assets are precompiled every time during the deploy process. rake assets:precompile can take up a noticable amount of time of a deploy.
Run the task manually:
cap production deploy:assets:precompile
to skip assets precompilation during deploy add this line to deploy.rb:
Rake::Task["deploy:assets:precompile"].clear_actions
- if we want to skip it only sometimes, we can add env variable:
if ENV['skip_assets']=='1'
Rake::Task["deploy:assets:precompile"].clear_actions
end
and run it as:
# this will skip precompilation
skip_assets=1 cap production deploy
# this will precompile assets
cap production deploy
We will redefine default precompile task to check for changed files. Assets will be precompiled only if changes are detected in certain files or folders defined by variable :assets_dependencies.
# set the locations that we will look for changed assets to determine whether to precompile
set :assets_dependencies, %w(app/assets lib/assets vendor/assets Gemfile config/routes.rb)
# clear the previous precompile task
Rake::Task["deploy:assets:precompile"].clear_actions
class PrecompileRequired < StandardError; end
namespace :deploy do
namespace :assets do
desc "Precompile assets if changed"
task :precompile_changed do
on roles(:app) do
within release_path do
with rails_env: fetch(:rails_env) do
begin
# find the most recent release
latest_release = capture(:ls, '-xr', releases_path).split[1]
# precompile if this is the first deploy
raise PrecompileRequired unless latest_release
#
latest_release_path = releases_path.join(latest_release)
# precompile if the previous deploy failed to finish precompiling
execute(:ls, latest_release_path.join('assets_manifest_backup')) rescue raise(PrecompileRequired)
fetch(:assets_dependencies).each do |dep|
#execute(:du, '-b', release_path.join(dep)) rescue raise(PrecompileRequired)
#execute(:du, '-b', latest_release_path.join(dep)) rescue raise(PrecompileRequired)
# execute raises if there is a diff
execute(:diff, '-Naur', release_path.join(dep), latest_release_path.join(dep)) rescue raise(PrecompileRequired)
end
warn("-----Skipping asset precompile, no asset diff found")
# copy over all of the assets from the last release
execute(:cp, '-rf', latest_release_path.join('public', fetch(:assets_prefix)), release_path.join('public', fetch(:assets_prefix)))
rescue PrecompileRequired
warn("----Run assets precompile")
execute(:rake, "assets:precompile")
end
end
end
end
end
end
end
This solution was found in the post https://coderwall.com/p/aridag/only-precompile-assets-when-necessary-rails-4-capistrano-3
Use the new task instead of the default precompile task:
namespace :deploy do
namespace :assets do
desc "Precompile assets if changed"
task :precompile do
on roles(:app) do
invoke 'deploy:assets:precompile_changed'
#Rake::Task["deploy:assets:precompile_changed"].invoke
end
end
end
end
Sometimes you need to precompile assets locally and upload them to the server.
Define a task to compile assets locally and copy them to the server. If your local machine is on Windows, make sure you have zip archiver (for example, 7zip).
namespace :deploy do
namespace :assets do
desc 'Run the precompile task locally and upload to server'
task :precompile_locally_archive do
on roles(:app) do
run_locally do
if RUBY_PLATFORM =~ /(win32)|(i386-mingw32)/
execute 'del "tmp/assets.tar.gz"' rescue nil
execute 'rd /S /Q "public/assets/"' rescue nil
# precompile
with rails_env: fetch(:rails_env) do
execute 'rake assets:precompile'
end
#execute "RAILS_ENV=#{rails_env} rake assets:precompile"
# use 7zip to archive
execute '7z a -ttar assets.tar public/assets/'
execute '7z a -tgzip assets.tar.gz assets.tar'
execute 'del assets.tar'
execute 'move assets.tar.gz tmp/'
else
execute 'rm tmp/assets.tar.gz' rescue nil
execute 'rm -rf public/assets/*'
with rails_env: fetch(:rails_env) do
execute 'rake assets:precompile'
end
execute 'touch assets.tar.gz && rm assets.tar.gz'
execute 'tar zcvf assets.tar.gz public/assets/'
execute 'mv assets.tar.gz tmp/'
end
end
# Upload precompiled assets
execute 'rm -rf public/assets/*'
upload! "tmp/assets.tar.gz", "#{release_path}/assets.tar.gz"
execute "cd #{release_path} && tar zxvf assets.tar.gz && rm assets.tar.gz"
end
end
end
end
If you don't want to archive the assets before upload, use this task which will copy folder /public/assets to the server file by file.
namespace :deploy do
namespace :assets do
desc 'Precompile assets locally and upload to server'
task :precompile_locally_copy do
on roles(:app) do
run_locally do
with rails_env: fetch(:rails_env) do
#execute 'rake assets:precompile'
end
end
execute "cd #{release_path} && mkdir public" rescue nil
execute "cd #{release_path} && mkdir public/assets" rescue nil
execute 'rm -rf public/assets/*'
upload! 'public/assets', "#{release_path}/public", recursive: true
end
end
end
end
Run tasks :
cap production deploy:assets:precompile_locally_archive
or
cap production deploy:assets:precompile_locally_copy
To replace default precompile task with the new task:
namespace :deploy do
namespace :assets do
desc "Precompile assets"
task :precompile do
on roles(:app) do
invoke 'deploy:assets:precompile_locally_archive'
#Rake::Task["deploy:assets:precompile_locally_archive"].invoke
end
end
end
end
Use these tasks if you need to show a certain page on site to visitors while the app is updating:
cap production deploy:web:enable
cap production deploy:web:disable
Create a page 'app/views/admin/maintenance.html.haml'
<div style="width:100%;">
<div style="width:900px; margin:0 auto;">
<h1>Site is offline for <%= reason ? reason : 'maintenance' %></h1>
<p>We're currently offline for <%= reason ? reason : 'maintenance' %> as of <%= Time.now.utc.strftime('%H:%M %Z') %>.</p>
<p>Sorry for the inconvenience.
<p>We'll be back <%= deadline ? "by #{deadline}" : 'shortly' %>.</p>
</div></div>
The task will compile the template and put it in 'shared/public/system/maintenance.html'
Tasks:
# maintenance page
namespace :deploy do
namespace :web do
desc <<-DESC
Present a maintenance page to visitors.
$ cap deploy:web:disable REASON="a hardware upgrade" UNTIL="12pm Central Time"
DESC
task :disable do
on roles(:web) do
execute "rm #{shared_path}/system/maintenance.html" rescue nil
require 'erb'
reason = ENV['REASON']
deadline = ENV['UNTIL']
template = File.read('app/views/admin/maintenance.html.haml')
#page = ERB.new(template).result(binding)
content = ERB.new(template).result(binding)
path = "public/system/maintenance.html"
File.open(path, "w") { |f| f.write content }
upload! path, "#{shared_path}/public/system/maintenance.html", :mode => 0644
end
end
task :enable do
on roles(:web) do
execute "rm #{shared_path}/public/system/maintenance.html"
end
end
end
end
The solution was found here: http://stackoverflow.com/questions/2244263/capistrano-to-deploy-rails-application-how-to-handle-long-migrations.
Run the tasks before and after deploy:
before "deploy", "deploy:web:disable"
after "deploy", "deploy:web:enable"
Now we need to show this page on site. You need to modify settings on your web server to use this maintenance page.
If you have Nginx as a web server, add this code to the server's configuration:
server {
passenger_enabled on;
server_name yoursite.com;
...
if (-f $document_root/system/maintenance.html) {
rewrite ^(.*)$ /system/maintenance.html break;
}
}
set :keep_releases, 5
Keep only several releases. Old releases will be deleted automatically after successful deploy.
Run this command after you change your config files (like config/database.yml)
cap production deploy:copy_config_files
cap production deploy
cap <stage_name> deploy
Tutorials about deploy