Jon Kinney - Home > Blog > Deploying to multiple server environments with cap

Deploying to multiple server environments with cap

 -Thursday, April 30, 2009 By: Jon Kinney

Most anytime I develop a web application I need to deploy to multiple server environments. For me this used to mean maintaining two separate deploy.rb scripts, and I would rename one while deploying to staging, and then rename the other when I needed to deploy to production. After about three deploys I said, the hell with this! And I figured out how to allow the specification of your deployment at the command line during the cap deploy task.

Now I know what you're thinking, why not create your own cap task that is called cap deploy_production and cap deploy_staging (or whatever), but I didn't want to muck around with that. Here is how I get Capistrano to prompt ME!

   1  default_run_options[:pty] = true
   2  ssh_options[:forward_agent] = true
   3  # also had to set up id_ras keys for the deploy user on the production box (to itself). And setup the tunnel definition as well in ~/.subversion/config
   4  # Local => rs_ssh = /opt/local/bin/ssh -p 2384 -l jkinney
   5  # Production => rs_ssh = /usr/bin/ssh -p 2384 -l jkinney
   6  
   7  set :gems_for_project, %w(highline,will_paginate,etc...) # list of gems to be installed
   8  
   9  # Make terminal prompt us for the location we want to deploy to
  10  set :deploy_location, Proc.new { deploy_location = Capistrano::CLI.ui.ask "Enter deploy location (stage/prod)" }
  11  
  12  if "#{deploy_location}" == "prod"
  13    set :domain, "209.41.75.42"
  14  else
  15    # I have ssh setup on a non-standard port for my staging box, since it's exposed to the world. Our production box required VPN or local netowrk access.
  16    ssh_options[:port] = 2384
  17    set :domain, "YOUR STAGING IP HERE"
  18    # note that your staging could be on the same server, just setup your application names differently then so you aren't overwriting things.
  19  end
  20  
  21  # prompts for a release tag, if that is your sort of thing...
  22  set :release_tag, Proc.new { release_tag = Capistrano::CLI.ui.ask "Enter a release tag to deploy (type trunk or leave blank and hit enter to deploy from trunk)" }
  23  
  24  role :app, domain
  25  role :web, domain
  26  role :db,  domain, :primary => true
  27  
  28  set :application, "jonkinneydotcom"
  29  
  30  set :user, "deploy"
  31  set :password, "secret"
  32  set :deploy_to, "/var/www/apps/#{application}"
  33  
  34  set :rails_env, "production"
  35  
  36  # I thought this was automatic, but I seem to need to require it to cleanup my releases
  37  set :keep_releases, 4
  38  after "deploy", "deploy:cleanup"
  39  
  40  # this is using subversion, I am transitioning to GIT and will post updates when I have a modified deploy.rb
  41  set :repo_location, "/var/svn/your_subversion_repo"
  42  set :repository, "svn+ssh://#{domain}#{repo_location}/#{application}/trunk"
  43  
  44  namespace :deploy do
  45    desc "restart passenger"
  46    task :restart, :roles => :app, :except => {:no_release => true} do
  47      run "touch #{current_path}/tmp/restart.txt"
  48    end
  49  
  50    [:start, :stop].each do |t|
  51      desc "#{t} task is a no-op with passenger"
  52      task t, :roles => :app do; end
  53    end
  54    
  55    task :after_symlink do
  56      run "chmod -R a+rw #{release_path}/public"
  57  
  58      #rcov messes with deployed apps...remove it in production
  59      run "rm -rf #{release_path}/vendor/plugins/rails_rcov" 
  60      
  61      # OPTIONAL: symlink files from the FTP site's home directory to the rails_root 
  62      # keep them protected and use send_file to present them to the logged in user 
  63      # (this is more secure than hiding them in a public directory with directory listing off)
  64      # run "rm -rf #{release_path}/admin_files" 
  65      # run "ln -s #{deploy_to}/#{shared_dir}/admin_files #{release_path}"    
  66  
  67      #symlink the files from outside the deploy path so we can keep all the uploaded images!
  68      # This will allow images uploaded through an asset manager to be retained between deployments
  69      run "rm -rf #{release_path}/public/assets" 
  70      run "ln -s #{deploy_to}/#{shared_dir}/assets #{release_path}/public/assets"
  71  
  72      # setup database for production environment (database.yml should be ignored in svn)
  73      db_params = {
  74        "adapter"=>"mysql",
  75        "database"=>"yourapp_production",
  76        "username"=>"root",
  77        "password"=>"secret",
  78        "host"=>"localhost",
  79      }
  80  
  81      # OPTIONAL: Deploy to production with a sqlite3 database...because we don't need anything elaborage. 
  82      # And that way we can reset the db every hour or whatever with a cron job
  83      # db_params = {
  84      #   "adapter"=>"sqlite3",
  85      #   "database"=>"db/production.sqlite3.db",
  86      #   "timeout"=>"5000"
  87      # }
  88  
  89      db_params.each do |param, default_val|
  90        set "db_#{param}".to_sym,
  91        #if you want to be prompted uncomment the line below this and comment out the one directly below that
  92        # lambda { Capistrano::CLI.ui.ask "Enter database #{param}" do |q| q.default=default_val end}
  93        param = default_val
  94      end
  95      
  96      # builds the database.yml
  97      database_configuration = "production:\n"
  98      db_params.each do |param, default_val|
  99        val=self.send("db_#{param}")
 100        database_configuration<<"  #{param}: #{val}\n"
 101      end
 102      
 103      run "mkdir -p #{deploy_to}/#{shared_dir}/config"
 104      
 105      put database_configuration, "#{deploy_to}/#{shared_dir}/config/database.yml"    
 106  
 107      #symlink the database.yml
 108      run "ln -s #{deploy_to}/#{shared_dir}/config/database.yml #{deploy_to}/current/config"
 109      
 110      #symlink the production database
 111      run "ln -s #{deploy_to}/#{shared_dir}/config/production.sqlite3.db #{deploy_to}/current/db"
 112    end
 113  end
 114  
 115  # This is what asks you if you're sure you want to deploy to production?!?!?
 116  before "deploy:update_code", "user_confirmation_for_production_deployment"
 117  task :user_confirmation_for_production_deployment, roles => :app do
 118    if "#{deploy_location}" == 'prod'
 119      message = "You are deploying to PRODUCTION. continue(y/n):"
 120      answer = Capistrano::CLI.ui.ask(message)   
 121      abort "deployment to production was stopped" unless answer == 'y'
 122    end
 123  end
 124  
 125  
 126  
 127  ######################Custom cap tasks that I find useful
 128  desc "Configure VHost"
 129  task :config_vhost do
 130    vhost_config =<<-EOF
 131  <VirtualHost *:80>
 132    ServerName jonkinney.com
 133    ServerAlias www.jonkinney.com
 134    DocumentRoot #{deploy_to}/current/public
 135  </VirtualHost>
 136    EOF
 137    put vhost_config, "#{deploy_to}/#{shared_dir}/config/vhost_config"
 138    sudo "mv #{deploy_to}/#{shared_dir}/config/vhost_config /etc/apache2/sites-available/#{application}"
 139    sudo "a2ensite #{application}"
 140    sudo "/etc/init.d/apache2 reload"
 141  end
 142  
 143  desc "create assets directory"
 144  task :create_assets_directory do
 145    sudo "mkdir -p #{deploy_to}/#{shared_dir}/assets"
 146    sudo "chmod -R 777 #{deploy_to}/#{shared_dir}/assets"
 147    sudo "chown -R deploy:www-data #{deploy_to}/#{shared_dir}/assets"
 148  end
 149  
 150  desc "make current development database the production database"
 151  task :upload_dev_db_to_prod do
 152    put(File.read("db/development.sqlite3.db"), "#{deploy_to}/#{shared_dir}/config/production.sqlite3.db")
 153  end
 154  
 155  desc "run remote command"
 156  task :show, :roles => :app do
 157    run <<-COMMAND
 158      /var/www/apps/#{application}/current/script/runner -e production 'require "pp"; pp #{command}'
 159    COMMAND
 160  end
 161  
 162  desc "run remote rake db:migrate RAILS_ENV=production"
 163  task :remote_db_migrate do
 164    run("cd #{deploy_to}/current; /usr/bin/rake db:migrate RAILS_ENV=production")
 165  end

Hopefully this has been helpful. I know Capistrano can be somewhat of a black box for people, so if you have issues with my deploy.rb or need any help feel free to contact me or post in the comments!

<< Next Newest Article   ||   Next Oldest Article >>

News & Events()

Tech Review: Web Design For Developers - June 2009

A friend of mine in the web development community is releasing a book called "Web Design for Developers: A Programmer's Guide to Design Tools and Techniques". I was asked to do a tech review of the book and will be sharing my thoughts as well as some cool info presented in the book in a short series of upcoming blog posts. If you want to grab a beta copy of the book head over to my favorite publisher The Pragmatic Programmers.

First Class Audio Production - March 1st 2009

My most recent studio project was mixing and producing the latest a cappella album to come out of Eau Claire, WI. Until Proven Guilty is the Innocent Men's 4th studio album and marks a huge leap forward in recording and production quality for the group. Check back for demos of the mastered songs very soon! http://theinnocentmen.com.

Rate This Post: 4.0 (average)

Twitter Feed (follow me)