Skip to main content

Professional deployment of websites using Capistrano - Part 3

Using Capistrano for deploying PHP and other non-Rails based websites

WarningPlease note that since Capistrano 3's release in October 2013 this Capistrano 2 based tutorial series has been superseded by an updated Capistrano 3 tutorial series.

Part three of my four part Capistrano series covers adapting the Ruby based Capistrano deployment tool for use with LAMP websites. If you missed the previous instalments of this Capistrano series then you can read parts one and two via our blog. This is a monumentally techie blog post which isn't for the faint hearted!

Within Part 2 we outlined our problem of experiencing clunky, error-ridden deploys with our existing LAMP based projects. To resolve this predicament we're investigating using Capistrano based deploys on our smallest project, "Example.com". We got as far as installing and configuring Capistrano on our Ubuntu 12.04 LTS based server and now we're in the position where we have a powerful Ruby based deploy tool for Rails based projects. Not exactly a great fit for our LAMP based Example.com project!

Fortuitously one of the authors of Capistrano, Lee Hambley, was kind enough to provide another less prolific RubyGem for Capistrano called 'Railsless Deploy'. This Gem, used in conjunction with a standard Capistrano installation replaces the deploy.rb file of Capistrano with a file called railsless-deploy.rb. As you might guess this version of deploy.rb doesn't include Rails specific functionality such as Active Record migrations using Rake which is present in the original.

It's worth noting as an aside at this point that last week Capistrano v3.0.0 was released which removes the Railsisms that were previously present in Capistrano v2.x. This version doesn't currently support SVN as a source control system so we can't switch to it for this SVN focussed tutorial. Also we're naturally cautious when it comes to adopting the latest versions of software so even if it did support SVN right now we'd give it a few months for the more adventurous developers to flush out any bugs in the initial release. Perhaps Capistrano 3.x will make a good unplanned 'Part 5' for this series. UPDATE: Our Capistrano 3 tutorial series is now available.

Installing the Railsless Deploy RubyGem is very simple. Log into the server you installed Capistrano on and run:


sudo gem install railsless-deploy

Now it's time to switch our standard Capistrano deploy script to be a Railsless deploy. Change directory to the location you created our 'example' project Capistrano script in Part 2 of this series:

	
cd /home/deploy/capistrano/example
	

Open the file Capfile in your editor of choice:

	
vi Capfile

And replace the files contents with the following:

	
require 'rubygems'
require 'railsless-deploy'
load    'config/deploy'

As you can see, this removes the original Capistrano deploy file and includes the railsless-deploy file instead. Now if you run:

	
cap –T

You will see a shorter list of Capistrano tasks than were displayed at the end of Part 2 of this series as the Rails specific tasks have all been removed.

So far so good

Now we have Capistrano setup so that it works with none Rails based applications. Let's create a minimal deploy script for our 'Example.com' application. Change into the config folder within the example project's deploy files and edit the deploy.rb file:

	
cd  /home/deploy/capistrano/example/config/
vi deploy.rb

You should see something along the lines of:

	
set :application, "set your application name here"
set :repository,  "set your repository location here"

# set :scm, :git # You can set :scm explicitly or Capistrano will make an intelligent guess based on known version control directory names
# Or: `accurev`, `bzr`, `cvs`, `darcs`, `git`, `mercurial`, `perforce`, `subversion` or `none`

role :web, "your web-server here"                          # Your HTTP server, Apache/etc
role :app, "your app-server here"                          # This may be the same as your `Web` server
role :db,  "your primary db-server here", :primary => true # This is where Rails migrations will run
role :db,  "your slave db-server here"

# if you want to clean up old releases on each deploy uncomment this:
# after "deploy:restart", "deploy:cleanup"

# if you're still using the script/reaper helper you will need
# these http://github.com/rails/irs_process_scripts

# If you are using Passenger mod_rails uncomment this:
# namespace :deploy do
#   task :start do ; end
#   task :stop do ; end
#   task :restart, :roles => :app, :except => { :no_release => true } do
#     run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
#   end
# end

We can delete the commented out sections relating to Passenger and the 'reaper helper'. This leaves us with the following:

		
set :application, "set your application name here"
set :repository,  "set your repository location here"

# set :scm, :git # You can set :scm explicitly or Capistrano will make an intelligent guess based on known version control directory names
# Or: `accurev`, `bzr`, `cvs`, `darcs`, `git`, `mercurial`, `perforce`, `subversion` or `none`

role :web, "your web-server here"                          # Your HTTP server, Apache/etc
role :app, "your app-server here"                          # This may be the same as your `Web` server
role :db,  "your primary db-server here", :primary => true # This is where Rails migrations will run
role :db,  "your slave db-server here"

# if you want to clean up old releases on each deploy uncomment this:
# after "deploy:restart", "deploy:cleanup"

You can find definitions of the majority of Capistrano configuration variables here: https://github.com/capistrano/capistrano/wiki/2.x-Significant-Configuration-Variables. Let's go ahead and fill in some values for the Staging site of Example.com and remove some extraneous comments:

		
set :application, "example"
set :repository,  "svn+ssh://svn.zodiacmedia.co.uk/example"

set :scm, :subversion

server "staging.zodiacmedia.co.uk", :app, :web, :db

after "deploy: update ", "deploy:cleanup"

The keen eyed amongst you will notice that:

  • We're using the svn+ssh protocol for Subversion access to make use of the architectural groundwork we did in Part 2 of this series.
  • We've condensed the various roles into a single line using a slightly different syntax. Capistrano is capable of interacting with multiple servers performing different roles but in our small Example.com project everything runs off of the one server so we can condense this into single line using an alternate syntax. See: https://github.com/capistrano/capistrano/wiki/2.x-From-The-Beginning#back-to-configuration for more details.
  • We've dropped the :primary => true syntax as this is used by Rails Active Record migrations which we're not using.
  • We've change the line after "deploy:restart", "deploy:cleanup" to read after "deploy: update ", "deploy:cleanup" task. Again, this is to accommodate the removal of Rails related functionality that we're not using.

The deploy script as it stands needs a little more configuration setting in order to make it functional:

		
set :application, "example"

set :scm, :subversion
set :repository,  "svn+ssh://svn.zodiacmedia.co.uk/example"
set :deploy_via, :export

server "staging.zodiacmedia.co.uk", :app, :web, :db
set :deploy_to, "/var/www/staging.example.com"

set :user, "deploy"
default_run_options[:pty] = true
ssh_options[:keys] = [File.join(ENV["HOME"], ".ssh", "id_rsa")]
ssh_options[:port] = 22
set :use_sudo, false

set :keep_releases, 5
after "deploy: update ", "deploy:cleanup"

To cover the new parts:

  • deploy_via defines the source control method you will be using to get your code out of your repository - we'll be using an SVN export.
  • deploy_to defines the directory on the file system where we want to deploy to.
  • The set:user... section instructs Capistrano to run its SSH sessions using the Linux user deploy. This corresponds to the user accounts we created in Part 2 of this series. If you've switched SSH sessions to run on a non-standard port then change 22 to be your non-standard value.
  • Setting default_run_options[:pty] = true results in Capistrano using a pseudo terminal. If everything goes to plan with this tutorial then this isn't strictly necessary, but it's useful to enable this setting so that user prompts (e.g. password requests) get shown in case something goes awry.
  • set :use_sudo, false - by default Capistrano will run some tasks using sudo. We don't want to do this for security reasons so we override it here.
  • set :keep_releases, 5 - this defines the total number of releases Capistrano should keep before deleting them from the file system. A value of 5 means that only the current release and the last 4 'old' releases will be retained. Everytime a new release is made then the oldest release is deleted. This is useful to stop your disk space being consumed by lots of old unused versions of Example.com

An SVN tag is a release

In Part 2 of this series we outlined how each SVN tag would be a release candidate. To make this viable we expand our script as follows:

		
set :application, "example"

set :scm, :subversion
set :repository_root, "svn+ssh://svn.zodiacmedia.co.uk/example"
set :deploy_via, :export

server "staging.zodiacmedia.co.uk", :app, :web, :db
set :deploy_to, "/var/www/staging.example.com"

set :user, "deploy"
default_run_options[:pty] = true
ssh_options[:keys] = [File.join(ENV["HOME"], ".ssh", "id_rsa")]
ssh_options[:port] = 22
set :use_sudo, false

set :keep_releases, 5
after "deploy: update", "deploy:cleanup"

set(:tag) { Capistrano::CLI.ui.ask("Enter SVN tag to deploy (or type 'trunk' to deploy from trunk): ") } 
set(:repository) { (tag == "trunk") ? "#{repository_root}/trunk" : "#{repository_root}/tags/#{tag}" }

Now when we run a Capistrano deploy we'll be prompted for the SVN tag we want to deploy.

Let's deploy to Staging

If the deploy_to folder doesn't exist on your target server create it using:

		
sudo mkdir -p /var/www/staging.example.com

Then change the folder ownership and permissions as follows:

		
sudo chown deploy:www-data /var/www/staging.example.com/

Then we need to run cap deploy:setup to setup the folder structure needed when deploying. Change directory:

		
cd /home/deploy/capistrano/example/

And run:

		
cap deploy:setup
		

You should be prompted to enter your SSH key when this command runs.

This will create the standard folder structure in the /var/www/staging.example.com folder which can be seen as follows:

		
ls –la /var/www/staging.example.com
 total 16
drwxrwxr-x  4 deploy www-data 4096 Oct 14 16:24 .
drwxr-xr-x 11 root   root     4096 Oct 14 16:24 ..
drwxrwxr-x  2 deploy deploy   4096 Oct 14 16:24 releases
drwxrwxr-x  5 deploy deploy   4096 Oct 14 16:24 shared

The releases folder will go on to contain subfolders where the contents of your codebase will be checked out to. When you actually create a successful release then a symlink with the identifier current will be added to the /var/www/staging.example.com folder. This will point to the latest SVN export in the releases folder. The shared folder is for placing assets shared across releases (e.g. user uploaded content etc) which will be symlinked to from your current release.

So let's make our first release.

	
cap deploy

You should be prompted to enter your SSH key password twice when Capistrano runs. Once for querying the SVN repository from your Capistrano server using the deploy user and a second time for using the deploy user account to establish an SSH session on the target deploy server.

If you list the contents of the staging.example.com directory you should see that the previously mentioned current symlink is now present and that the releases folder has a child folder whose name is the date and time corresponding to the release time.

For increased efficiency you can avoid the SSH key prompts when Capistrano runs by using ssh-agent. Try it by typing:

	
ssh-agent bash
ssh-add

And then enter in your SSH key password when prompted. Now when you run cap deploy it should run straight through. We've now achieved our goal of one line deploys.

Multi Stage deployments

The current recipe deploys only to Staging. Moving to Production and Staging deploys is easy. Change the deploy.rb recipe to read:

	
set :application, "example"

set :stages, %w(production staging)
set :default_stage, "staging"
require 'capistrano/ext/multistage'

set :scm, :subversion
set :repository_root, "svn+ssh://svn.zodiacmedia.co.uk/example"
set :deploy_via, :export

#server "staging.zodiacmedia.co.uk", :app, :web, :db
#set :deploy_to, "/var/www/staging.example.com"

set :user, "deploy"
default_run_options[:pty] = true
ssh_options[:keys] = [File.join(ENV["HOME"], ".ssh", "id_rsa")]
ssh_options[:port] = 22
set :use_sudo, false

set :keep_releases, 5
after "deploy: update", "deploy:cleanup"

set(:tag) { Capistrano::CLI.ui.ask("Enter SVN tag to deploy (or type 'trunk' to deploy from trunk): ") } 
set(:repository) { (tag == "trunk") ? "#{repository_root}/trunk" : "#{repository_root}/tags/#{tag}" }

Now within the /home/deploy/capistrano/example/config folder create a folder called deploy

	
mkdir /home/deploy/capistrano/example/config/deploy

And within this folder create two files called production.rb and staging.rb. These files should be owned by the deploy user and group:

	
cd /home/deploy/capistrano/example/config/deploy/
touch production.rb staging.rb
sudo chown -R deploy:deploy /home/deploy/capistrano/example/config/deploy

The contents of production.rb should be:

	
server "www.example.com", :app, :web, :db
set :deploy_to, "/var/www/www.example.com"

and staging.rb should read:

	
server "staging.zodiacmedia.co.uk", :app, :web, :db
set :deploy_to, "/var/www/staging.example.com"

Note that we commented out the equivalent lines in the master deploy recipe.rb file.

Now to deploy to Staging we would change to the /home/deploy/capistrano/example folder and run:

	
cap deploy

Whereas to deploy to Production we would run this command from the same folder:

	
cap production deploy

Phew!

That's a lot of Capistrano, but you can reward yourself with the knowledge that from now on deployment is a single line command. Congratulations!