Skip to content

Instantly share code, notes, and snippets.

@u007 u007/
Last active Feb 17, 2017

What would you like to do?
full git deployment with auto boot of puma for ruby on rails via rvm, with nightly restart of rails


  • 1st, add production origin to git.
  • Next, ssh to remote and do a git init
  • Then on local git:
    • git pull production master
    • git push production master
ln -s /home/username/rails/production /home/username/rails/current
mkdir -p ~/rails/shared/config
mkdir -p ~/rails/shared/log
mkdir -p ~/rails/shared/vendor/bundle
mkdir -p ~/rails/shared/log
mkdir -p ~/rails/shared/tmp/pids
mkdir -p ~/rails/shared/tmp/cache
mkdir -p ~/rails/shared/tmp/sockets
  • upload secrets.yml, elasticsearch.yml to ~/rails/shared/config/
  • make sure not to commit config/secrets.yml into git, remove it if you have done so, use this:
git rm --cached config/secrets.yml# back this file 1st to somewhere ele
# add this to gitignore
  • Next, add user to /etc/group, find for line with "sudo" and add the user at the end of the line, separated by comma.
  • Next, setting up ruby on rails, install rvm first. Then:
rvm install 2.3.3
rvm use 2.3.3
gem install bundler
gem install puma
  • Next, goto rails root directory:
cd ~/rails/current
rvm 2.3.3 do bundle install --deployment --without development test

  • Then run load schema, seeds and then migration
export RAILS_ENV=production
rvm 2.3.3 do bin/rake db:schema:load
rvm 2.3.3 do bin/rake db:migrate
rvm 2.3.3 do bin/rake db:seed
  • then setup cronjob to restart instance every nightly:
crontab -e
0 1 * * * bash -lc 'cd /home/username/rails/current && rvm 2.3.3 do bundle exec pumactl -P tmp/pids/ phased-restart'
  • then setup rc.local to auto start instance when rebooted / booted (follow instruction in file)
  • then setup nginx to proxy application onto rails backend
mkdir -p ~/log
  • create the file nginx-[domainname].conf to /etc/nginx/sites-enabled
service nginx restart
  • and finally, setup /etc/logrotate.d/rails to auto clean up your log/production.log

  • that is all :), simply git commit, then git push production master, and the .git/hook/post-receive will do it's job

# located at /etc/nginx/sites-enabled/
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
upstream railsapp {
#server unix:///home/username/rails/shared/tmp/sockets/puma.sock;
server {
listen 80;
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/nginx.crt;
ssl_certificate_key /etc/nginx/ssl/nginx.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
# ~2 seconds is often enough for most folks to parse HTML/CSS and
# retrieve needed images/icons/frames, connections are cheap in
# nginx so increasing this is generally safe...
keepalive_timeout 5;
# path for static files
access_log /home/username/log/nginx.access.log;
error_log /home/username/log/nginx.error.log info;
# this rewrites all the requests to the maintenance.html
# page if it exists in the doc root. This is for capistrano's
# disable web task
if (-f $document_root/maintenance.html) {
rewrite ^(.*)$ /maintenance.html last;
location / {
root /home/username/rails/current/public;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $http_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header REMOTE_ADDR $http_x_real_ip;
proxy_set_header Host $http_host;
# If the file exists as a static file serve it directly without
# running all the other rewrite tests on it
if (-f $request_filename) {
# check for index.html for directory index
# if it's there on the filesystem then rewrite
# the url to add /index.html to the end of it
# and then break to send it to the next config rules.
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
# this is the meat of the rack page caching config
# it adds .html to the end of the url and then checks
# the filesystem for that file. If it exists, then we
# rewrite the url to have explicit .html on the end
# and then send it on its way to the next config rule.
# if there is no file on the fs then it sets all the
# necessary headers and proxies to our upstream pumas
if (-f $request_filename.html) {
rewrite (.*) $1.html break;
if (!-f $request_filename) {
proxy_pass http://railsapp;
# Now this supposedly should work as it gets the filenames with querystrings that Rails provides.
# BUT there's a chance it could break the ajax calls.
# location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ {
# expires max;
# break;
# }
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
# Error pages
# error_page 500 502 503 504 /500.html;
location = /500.html {
root /home/username/rails/current/public;
#!/usr/bin/env bash
# make sure to chmod a+x post-receive
# located at: .git/hooks/post-receive
export RUBYVERSION=2.3.3
source /etc/profile
cd ..
export PWD=`pwd`
echo "cwd: $PWD"
export RAILS_ENV=production
git reset --hard
git checkout master
unset GIT_DIR
echo "------> Bundling gems!!"
rvm $RUBYVERSION do bundle --deployment --without development test
if [ -d "../shared/" ] ; then
echo "------> copying configs"
cp -rf ../shared/config/* ./config/
ln -s "$PWD/../shared/log" ./
ln -s "$PWD/../shared/public/uploads" ./public/
ln -s "$PWD/../shared/public/system" ./public/
# ln -s "$PWD/../shared/tmp" ./
ln -s "$PWD/../shared/vendor/bundle" ./vendor/
ln -s "$PWD/../shared/tmp/pids" ./tmp/
ln -s "$PWD/../shared/tmp/cache" ./tmp/
ln -s "$PWD/../shared/tmp/sockets" ./tmp/
echo "------> running bundle exec rake server:deploy"
rvm $RUBYVERSION do bin/rake server:deploy
# thanks to blazing gem, extracted script from them
# located at config/puma.rb
# if you have more ram, increase this thread
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 2 }.to_i
threads 1, threads_count
# Specifies the `port` that Puma will listen on to receive requests, default is 3000.
port ENV.fetch("PORT") { 3000 }
daemonize true
pidfile 'tmp/pids/'
state_path 'tmp/pids/puma.state'
# Specifies the `environment` that Puma will run in.
environment ENV.fetch("RAILS_ENV") { "production" }
# if you have more core and also more ram, enable this and increase if needed.
# not recommended for system below 2GB of ram
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory. If you use this option
# you need to make sure to reconnect any threads in the `on_worker_boot`
# block.
# preload_app!
# do not use preload if you want to use phased-restart
# located at: /etc/rc.local
su username_here /bin/bash -lc 'cd /home/userdir_here/rails/current && rvm 2.3.3 do bundle exec puma -C config/puma.rb' &
# located at lib/tasks/server.rake
namespace :server do
desc 'Deplying to server'
task deploy: :environment do
asset_precompilation_triggers = %w(app/assets vendor/assets Gemfile.lock config)
puts "ok"
if (deployed_file_exists?("db/schema.rb") || deployed_file_exists?("db/structure.sql")) && trigger_update?("db/")
puts 'post_deploy: migrating..'
rvm_run "bin/rake db:migrate 2>&1"
if asset_precompilation_triggers.detect {|path| trigger_update?(path)}
puts 'post_deploy: ignoring locally assets:precompile.'
# system "bundle exec rake assets:precompile 2>&1"
# rvm_run "bundle exec rake rails:update:bin 2>&1"
# system "bundle exec rake server:restart 2>&1"
desc 'Restarting server'
task restart: :environment do
puts 'Restarting...'
def rvm_run(cmd)
cmd1 = "/usr/local/rvm/bin/rvm 2.3.3 do #{cmd}" "running: #{cmd1}"
system cmd1
def shared_path
shared_path = "`pwd`/../shared"
def restart_server
system "pumactl -P tmp/pids/ phased-restart"# phased restart only useful if you have more than 1 worker
# system "puma -C #{shared_path}/puma.rb"
# system "touch tmp/restart.txt 2>&1"
# if you use sidekiq
rvm_run "bundle exec sidekiqctl stop tmp/ 0"
rvm_run "bundle exec sidekiq -d -P tmp/ -L log/sidekiq.log"
# if you use delayed job
# rvm_run "bin/delayed_job -n 2 restart"
def deploy_to
# Capture the result of a git command run within the `deploy_to` directory
def capture_git(command)
`cd #{deploy_to} && git #{command}`
def trigger_update?(path)
# puts "updated? #{path} in #{changed_files.inspect}"
changed_files.detect {|p| p[0, path.length] == path}
def changed_files
# puts "latest tag: #{latest_tag}"
cnt = latest_merge_count
# puts "commit since last push: #{cnt}"
@changed_files = capture_git('diff --name-only HEAD~'+cnt.to_s+' | cat').split
# @changed_files ||= if latest_tag
# capture_git("diff --name-only #{latest_tag} origin/#{branch} | cat").split
# else
# capture_git("ls-files | cat").split
# end
def deployed_file_exists?(path)
exit_code("cd #{deploy_to} && [ -f #{path} ]") == "0"
# Does the given directory exist within the deployment directory?
def deployed_dir_exists?(path)
exit_code("cd #{deploy_to} && [ -d #{path} ]") == "0"
# Has the given path been created or changed since the previous deployment? During the first
# successful deployment this will always return true if the file exists.
def deployed_file_changed?(path)
return deployed_file_exists?(path) unless latest_tag
exit_code("cd #{deploy_to} && git diff --exit-code #{latest_tag} origin/#{branch} #{path}") == "1"
def exit_code(command)
# puts "cmd: #{command} > /dev/null 2>&1; echo $?".strip
# Ammeter.OutputCapturer.capture("#{command} > /dev/null 2>&1; echo $?").strip
`#{command} > /dev/null 2>&1; echo $?`.strip
def latest_merge_count
if File.exists?("../shared/git_count")
res = `cat ../shared/git_count`.strip.to_i
new_count = capture_git('rev-list --all --count').to_i
return new_count - res
return 1
def save_commit_count
new_count = capture_git('rev-list --all --count').to_i'../shared/git_count', 'w') { |file| file.write(new_count.to_s) }
def latest_tag
def release_matcher
def latest_tag_from_repository
tags = capture_git("tag").strip.split
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.