Skip to main content

Capistrano 3 Tutorial Series Part 2 - Using Capistrano 3 for deploying PHP and other non-Rails based websites

Welcome to part two in our three part Capistrano 3 tutorial series. If you worked your way through part one then you should be all set to write your first Capistrano deploy script. The deploy script we'll create will be a "bare bones" script which simply moves files from an SVN tag to a folder on your Staging server.

Compared to part one this tutorial instalment is a walk in the park. Enjoy!

Making Capistrano 3 work with non-Rails based projects

Unlike Capistrano 2, Capistrano 3 is not written for default usage with Rails projects. This makes things simpler as we don't have to do anything special for Capistrano to work with our LAMP project.

Let's create a minimal deploy script for our 'Example.com' application. Change into the config folder within the Example project's deploy script 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, 'my_app_name'
set :repo_url, 'git@example.com:me/my_repo.git'

# ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }

# set :deploy_to, '/var/www/my_app'
# set :scm, :git

# set :format, :pretty
# set :log_level, :debug
# set :pty, true

# set :linked_files, %w{config/database.yml}
# set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}

# set :default_env, { path: "/opt/ruby/bin:$PATH" }
# set :keep_releases, 5

namespace :deploy do

  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      # Your restart mechanism here, for example:
      # execute :touch, release_path.join('tmp/restart.txt')
    end
  end

  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      # Here we can do anything such as:
      # within release_path do
      #   execute :rake, 'cache:clear'
      # end
    end
  end

  after :finishing, 'deploy:cleanup'

end

We can get rid of a lot of the example and informational lines and fill in some of our known settings:

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

set :ssh_options, {
  user: 'deploy'
}

set :scm, :svn

set :format, :pretty
set :log_level, :debug

set :keep_releases, 5

namespace :deploy do
  after :finishing, 'deploy:cleanup'
end

This file contains only the shared settings that are common to all of our deployment environments (Staging and Production). Let's set our Staging specific settings file now.

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

By default this reads:

set :stage, :staging

# Simple Role Syntax
# ==================
# Supports bulk-adding hosts to roles, the primary
# server in each group is considered to be the first
# unless any hosts have the primary property set.
role :app, %w{deploy@example.com}
role :web, %w{deploy@example.com}
role :db,  %w{deploy@example.com}

# Extended Server Syntax
# ======================
# This can be used to drop a more detailed server
# definition into the server list. The second argument
# something that quacks like a hash can be used to set
# extended properties on the server.
server 'example.com', user: 'deploy', roles: %w{web app}, my_property: :my_value

# you can set custom ssh options
# it's possible to pass any option but you need to keep in mind that net/ssh understand limited list of options
# you can see them in [net/ssh documentation](http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start)
# set it globally
#  set :ssh_options, {
#    keys: %w(/home/rlisowski/.ssh/id_rsa),
#    forward_agent: false,
#    auth_methods: %w(password)
#  }
# and/or per server
# server 'example.com',
#   user: 'user_name',
#   roles: %w{web app},
#   ssh_options: {
#     user: 'user_name', # overrides user setting above
#     keys: %w(/home/user_name/.ssh/id_rsa),
#     forward_agent: false,
#     auth_methods: %w(publickey password)
#     # password: 'please use keys'
#   }
# setting per server overrides global ssh_options

# fetch(:default_env).merge!(rails_env: :staging)

Again let's ditch the commented example lines and fill in our specific settings for our Staging deployment server:

set :stage, :staging

server 'staging.zodiacmedia.co.uk', roles: %w{web app db}, port: 22

set :deploy_to, '/var/www/staging.example.com'

Note that you need to change port 22 to the appropriate number if you run SSH sessions on a non-standard port.

We're now in a position to do some testing:

cd /home/deploy/capistrano/example
cap staging svn:check

This should show you the results of an svn info query on your target repository. If you run into issues it'll almost certainly be due to SSH permissions. To debug this you can use the tactic of switching to the deployuser's Linux account and trying to run the svn info command from that Linux account. For example:

sudo –s
su deploy
svn info svn+ssh://svn.zodiacmedia.co.uk/example

If you can run the svn info query ok then continue your debugging by running the same commands Capistrano is trying to run using the same Linux users and relevant servers.

When you run cap staging svn:check you'll be prompted to enter your SSH key's password. This can get quite tiresome and can be avoided by saving your key to ssh-agent for the duration of your SSH session as follows:

ssh-agent bash
ssh-add

Then type in your SSH key's password.

Getting ready to 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 staging deploy:check to setup the folder structure needed when deploying. Change directory:

cd /home/deploy/capistrano/example/

And run:

cap staging deploy:check

You should be prompted to enter your SSH key when this command runs unless you've saved in ssh-agent.

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
drwxr-xr-x  4 deploy www-data 4096 Nov 29 09:19 .
drwxr-xr-x 12 root   root     4096 Nov 29 09:17 ..
drwxrwxr-x  6 deploy deploy   4096 Nov 29 16:21 releases
drwxrwxr-x  2 deploy deploy   4096 Nov 29 09:19 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.

Before we make our first deploy let's refresh our memory on our deploy strategy.

An SVN tag is a release

Previously we outlined how each SVN tag would be a release candidate. To make this viable the svn.rake file we added to our Capistrano installation earlier prompts us to enter an svn_location when we run a deploy. This defaults to trunk but if we wanted to deploy a specific tag then we could enter tags/TAGNAME where TAGNAME is replaced by an actual tag name e.g. 1.12 or 0.43 etc. We tend to use incremental numbers for tag names as it's easier to keep track of releases that way.

So let's make our first release.

cap staging deploy

Enter the tag name you want to deploy, et voilà, your one line deploy of code from Subversion to Staging is complete!

We're now in a position where we can move on to the more powerful functionality of Capistrano, creating bespoke tasks in the deploy script for a project. In the next installment in this tutorial series we'll expand on the "bare bones" deploy script we've just created and evolve it into a script for deploying a Drupal powered website.