Skip to content

Instantly share code, notes, and snippets.

@Ex-Ark
Last active February 25, 2022 15:31
Show Gist options
  • Save Ex-Ark/89c92c40ae452c499b09d6defcca2754 to your computer and use it in GitHub Desktop.
Save Ex-Ark/89c92c40ae452c499b09d6defcca2754 to your computer and use it in GitHub Desktop.
Rails 6 + webpack assets + capistrano + unicorn + nginx : Zero downtime deploy and code reload
cap staging deploy:check
cap staging deploy
# /etc/nginx/sites-available/APP.conf
upstream APP {
# Path to Unicorn socket file
server unix:/home/USER/deployed_apps/APP/current/tmp/sockets/unicorn.sock fail_timeout=0;
}
server {
listen 80;
server_name XXXX.XXXXX.XXXXX;
root /home/USER/deployed_apps/APP/current;
try_files $uri/index.html $uri @APP;
location @APP {
proxy_pass http://APP;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
}
# serve a static "maintenance" html page in case unicorn is not started yet
error_page 500 502 503 504 /error/5xx.html;
location ^~ /error/ {
internal;
root /var/www;
}
client_max_body_size 4G;
keepalive_timeout 10;
}
# Load DSL and set up stages
require "capistrano/setup"
# Include default deployment tasks
require "capistrano/deploy"
# Load the SCM plugin appropriate to your project:
#
# require "capistrano/scm/hg"
# install_plugin Capistrano::SCM::Hg
# or
# require "capistrano/scm/svn"
# install_plugin Capistrano::SCM::Svn
# or
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git
# Include tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
# https://github.com/capistrano/rvm
# https://github.com/capistrano/rbenv
# https://github.com/capistrano/chruby
# https://github.com/capistrano/bundler
# https://github.com/capistrano/rails
# https://github.com/capistrano/passenger
#
require "capistrano/rvm"
# require "capistrano/rbenv"
# require "capistrano/chruby"
require "capistrano/bundler"
require "capistrano/rails/assets"
require "capistrano/rails/migrations"
require 'capistrano3/unicorn'
# require "capistrano/passenger"
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
# config/deploy.rb
lock "~> 3.11.2"
set :application, "APP"
set :user, "USER"
set :repo_url, "git@XXXX.com:XXXX/XXXXX.git"
set :migration_role, :app
set :conditionally_migrate, true
set :assets_roles, [:USER]
# clean assets every 2 deploys
set :keep_assets, 2
set :deploy_to, "/home/#{fetch(:user)}/deployed_apps/#{fetch(:application)}"
append :linked_files, "config/database.yml"
append :linked_files, "config/secrets.yml"
append :linked_files, "config/master.key"
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "tmp/logs"
namespace :deploy do
namespace :check do
# upload your local master.key in case it's missing on the deploy target
before :linked_files, :set_master_key do
on roles(:app), in: :sequence, wait: 10 do
unless test("[ -f #{shared_path}/config/master.key ]")
upload! 'config/master.key', "#{shared_path}/config/master.key"
end
end
end
end
task :assets do
on roles(:app) do
within release_path do
# not using rails_env will probably result in this: https://github.com/capistrano/rails/issues/237
with rails_env: fetch(:rails_env) do
execute :bundle, 'exec rails assets:precompile'
end
end
end
end
task :restart do
on roles(:app) do
invoke 'unicorn:restart'
end
end
before "deploy:assets:precompile", "deploy:assets"
after 'deploy:publishing', 'deploy:restart'
end
# ...
gem 'rails', '~> 6.0.0'
gem 'webpacker'
# to complicate things we could have different web server
# DO NOT USE if and block logic, otherwise the Gemfile.lock will be incorrect on each deployment
# Use Puma as the app server on Windows
gem 'puma', '~> 3.11', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
# Use unicorn on debian
gem 'unicorn', platform: :ruby
group :development do
gem "capistrano", "~> 3.10", require: false
gem "capistrano-rails", "~> 1.4", require: false
gem 'capistrano-rvm', require: false
gem 'capistrano3-unicorn', require: false
end
# config/deploy/production.rb
# default branch is master
set :rails_env, 'production'
set :unicorn_env, :production
set :unicorn_rack_env, 'production'
server "YYYY.YYY.YYYYY",
user: "USER",
roles: %w{web app db}
# config/deploy/staging.rb
set :branch, ENV['BRANCH'] || 'develop'
set :rails_env, 'production'
set :unicorn_env, :production
set :unicorn_rack_env, 'production'
server "XXX.XXX.XXXX",
user: "USER",
roles: %w{web app db}
# config/unicorn/production.rb
# set path to the application
root = "/home/USER/deployed_apps/APP/current"
working_directory root
# Set unicorn options
worker_processes 2
preload_app true
timeout 30
# Path for the Unicorn socket
listen "#{root}/tmp/sockets/unicorn.sock", :backlog => 64
# Set path for logging
stderr_path "#{root}/log/unicorn.stderr.log"
stdout_path "#{root}/log/unicorn.stdout.log"
# Set proccess id path
pid "#{root}/tmp/pids/unicorn.pid"
before_fork do |server, worker|
if defined?(ActiveRecord::Base)
ActiveRecord::Base.connection.disconnect!
end
# Before forking, kill the master process that belongs to the .oldbin PID.
# This enables 0 downtime deploys and cleans master(old) hanging around
old_pid = "#{server.config[:pid]}.oldbin"
if File.exists?(old_pid) && server.pid != old_pid
begin
Process.kill("QUIT", File.read(old_pid).to_i)
rescue Errno::ENOENT, Errno::ESRCH
# someone else did our job for us
end
end
end
after_fork do |server, worker|
if defined?(ActiveRecord::Base)
ActiveRecord::Base.establish_connection
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment