Capistrano for Expression Engine 2
UPDATE: There is actually a non-rails version of Capistrano here, I haven’t tried it yet but it’s probably a much more similar approach. Also it gets rid of loads of pointless rails rake tasks.
Assumptions:
- You’re comfortable with the command line and ssh
- You know how to install Ruby Gems
- You are working on unix environments
This is a guide to capifying Expression Engine 2 or any non rails site for that matter. Capistrano is a lovely clean way to deploy websites, you can find more about it here.
Now if we assume you have a website on your local machine and want to deploy it to some remote server. After you’ve installed the Capistrano gem, you should set up your ssh keys. You basically need to be able connect to passwordlessly in the following ways.
- From your local server connect to the repository server(SVN,git, etc);
- From your local server connect to the remote server;
- From your remote server connect to the repository server
Ok with the keys setup you just need to create the folder structure on the remote site. Connect to the remote server and navigate to the directory where your site will be deployed to. Create two directories ‘releases’ and ‘shared’ here, make sure their permissions is writable for you.
With that setup drop down to your local machine again, you need to run the following command in the root directory of the website, the example I’m using is at /var/www/vhosts/example.local/.
capify .
This will create the following files
/Capfile
/config/deploy.rb
Now open up the deploy.rb file in your favourite text editor. You need to change four values in this file.
The first :application - This name doesn’t really matter, just pick something appropriate.
Second is :repository, this should be the command you use to check-out the website. So something like
svn+ssh://svn-repository.co.uk/example
Thirdly :deploy_to, set this to the file path on the remote server you’re going to deploy to.
set :deploy_to, "/var/www/vhosts/example/"
Finally just add the website address to :web. This is what to ssh to when deploying.
role :web, "example.co.uk"
Also I don’t have sudo on my current hosting package so I include the following.
set :use_sudo, false
Now if you run
cap deploy
the site will be deployed but there will be a number of errors, it will also finish with a reaper error. These errors are a result of capistrano’s assumption that it’s deploying a rails site. For reference my trace is below.
* executing `deploy'
* executing `deploy:update'
** transaction: start
* executing `deploy:update_code'
executing locally: "svn+ssh://svn-repository.co.uk/example -rHEAD"
* executing "svn checkout -q -r147 svn+ssh://svn-repository.co.uk/example /var/www/vhosts/www.example.co.uk/releases/20110414084921 && (echo 147 > /var/www/vhosts/www.example.co.uk/releases/20110414084921/REVISION)"
servers: ["example.co.uk"]
[example.co.uk] executing command
command finished
* executing `deploy:finalize_update'
* executing "chmod -R g+w /var/www/vhosts/www.example.co.uk/releases/20110414084921"
servers: ["example.co.uk"]
[example.co.uk] executing command
command finished
* executing "rm -rf /var/www/vhosts/www.example.co.uk/releases/20110414084921/log /var/www/vhosts/www.example.co.uk/releases/20110414084921/public/system /var/www/vhosts/www.example.co.uk/releases/20110414084921/tmp/pids &&\\\n mkdir -p /var/www/vhosts/www.example.co.uk/releases/20110414084921/public &&\\\n mkdir -p /var/www/vhosts/www.example.co.uk/releases/20110414084921/tmp &&\\\n ln -s /var/www/vhosts/www.example.co.uk/shared/log /var/www/vhosts/www.example.co.uk/releases/20110414084921/log &&\\\n ln -s /var/www/vhosts/www.example.co.uk/shared/system /var/www/vhosts/www.example.co.uk/releases/20110414084921/public/system &&\\\n ln -s /var/www/vhosts/www.example.co.uk/shared/pids /var/www/vhosts/www.example.co.uk/releases/20110414084921/tmp/pids"
servers: ["example.co.uk"]
[example.co.uk] executing command
command finished
* executing "find /var/www/vhosts/www.example.co.uk/releases/20110414084921/public/images /var/www/vhosts/www.example.co.uk/releases/20110414084921/public/stylesheets /var/www/vhosts/www.example.co.uk/releases/20110414084921/public/javascripts -exec touch -t 201104140849.32 {} ';'; true"
servers: ["example.co.uk"]
[example.co.uk] executing command
*** [err :: example.co.uk] find: /var/www/vhosts/www.example.co.uk/releases/20110414084921/public/images: No such file or directory
*** [err :: example.co.uk] find: /var/www/vhosts/www.example.co.uk/releases/20110414084921/public/stylesheets: No such file or directory
*** [err :: example.co.uk] find: /var/www/vhosts/www.example.co.uk/releases/20110414084921/public/javascripts: No such file or directory
command finished
* executing `deploy:symlink'
* executing "rm -f /var/www/vhosts/www.example.co.uk/current && ln -s /var/www/vhosts/www.example.co.uk/releases/20110414084921 /var/www/vhosts/www.example.co.uk/current"
servers: ["example.co.uk"]
[example.co.uk] executing command
command finished
** transaction: commit
* executing `deploy:restart'
* executing "/var/www/vhosts/www.example.co.uk/current/script/process/reaper"
/usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/servers.rb:75:in `role_list_from': unknown role `app' (ArgumentError)
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/servers.rb:73:in `map'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/servers.rb:73:in `role_list_from'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/servers.rb:45:in `find_servers'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/servers.rb:9:in `find_servers_for_task'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/connections.rb:133:in `execute_on_servers'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/actions/invocation.rb:171:in `run_tree'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/actions/invocation.rb:143:in `run'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/actions/invocation.rb:89:in `send'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/actions/invocation.rb:89:in `invoke_command'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/recipes/deploy.rb:123:in `try_sudo'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/recipes/deploy.rb:136:in `try_runner'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/namespaces.rb:186:in `send'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/namespaces.rb:186:in `method_missing'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/recipes/deploy.rb:302:in `load'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/execution.rb:139:in `instance_eval'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/execution.rb:139:in `invoke_task_directly_without_callbacks'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/callbacks.rb:27:in `invoke_task_directly'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/execution.rb:89:in `execute_task'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/namespaces.rb:186:in `send'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/namespaces.rb:186:in `method_missing'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/namespaces.rb:104:in `restart'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/recipes/deploy.rb:154:in `load'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/execution.rb:139:in `instance_eval'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/execution.rb:139:in `invoke_task_directly_without_callbacks'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/callbacks.rb:27:in `invoke_task_directly'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/execution.rb:89:in `execute_task'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/configuration/execution.rb:101:in `find_and_execute_task'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/cli/execute.rb:45:in `execute_requested_actions_without_help'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/cli/execute.rb:44:in `each'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/cli/execute.rb:44:in `execute_requested_actions_without_help'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/cli/help.rb:19:in `execute_requested_actions'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/cli/execute.rb:33:in `execute!'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano/cli/execute.rb:14:in `execute'
from /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/bin/cap:4
from /usr/bin/cap:19:in `load'
from /usr/bin/cap:19
Removing the Errors
To remove these errors we need to override some of the default tasks. So we need to identify the tasks that contain the problem actions. If you scan over the trace you’ll see a number of “* executing `deploy:finalize_update’” lines, some contain the word deploy, these are the tasks in deploy script. So the deployment script performs the following tasks.
- executing `deploy’
- executing `deploy:update’
- executing `deploy:update_code’
- executing `deploy:finalize_update’
- executing `deploy:symlink’
- executing `deploy:restart’
These are the series of tasks that get performed during deployment. There are two main errors, one calling find on public/images, public/stylesheets and public/javascripts. The second is `role_list_from’: unknown role `app’ (ArgumentError) error.
The first problem with the find command occurs in “deploy:finalize_update”, so we’ll add that task to our own deploy.rb script to stop the ‘find’ call being made. finalize_update doesn’t really contain anything relevent to a Php site but if you’re curious you should navigate to the directory containing the capistrano gem for me that’s /usr/lib/ruby/gems/1.8/gems/capistrano-2.5.8/lib/capistrano. Then use the following:
grep 'finalize_update' . -R --color -A20
It should look something like this
task :finalize_update, :except => { :no_release => true } do
run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true)
# mkdir -p is making sure that the directories are there for some SCM's that don't
# save empty folders
run <<-CMD
rm -rf #{latest_release}/log #{latest_release}/public/system #{latest_release}/tmp/pids &&
mkdir -p #{latest_release}/public &&
mkdir -p #{latest_release}/tmp &&
ln -s #{shared_path}/log #{latest_release}/log &&
ln -s #{shared_path}/system #{latest_release}/public/system &&
ln -s #{shared_path}/pids #{latest_release}/tmp/pids
CMD
if fetch(:normalize_asset_timestamps, true)
stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
asset_paths = %w(images stylesheets javascripts).map { |p| "#{latest_release}/public/#{p}" }.join(" ")
run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }
end
end
We should probably just include the first line of the task in our deploy.rb, so we have something like this.
role :web, "example.co.uk"
set :use_sudo, false
namespace :deploy do
desc 'Complete final actions before switching over symlink'
task :finalize_update, :except => { :no_release => true } do
run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true)
end
end
So if we try to deploy now we’ll no longer get the find errors. We just have the reaper error to solve now. This error occurs during deploy:restart, if you grep for that task it will look something like this.
task :restart, :roles => :app, :except => { :no_release => true } do
try_runner "#{current_path}/script/process/reaper"
end
Now our php site won’t have this /script/process/reaper script so we just add an empty task to our deploy.rb. So it should look something like this.
namespace :deploy do
desc 'Complete final actions before switching over symlink'
task :finalize_update do
run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true)
end
desc 'Overrides default action to restart the server - not needed for Expression Engine site'
task :restart, :roles => :app, :except => { :no_release => true } do
end
end
Out deployment script will work fine now. This is just a couple of extra actions we’re going to include so deployment better suits an Expression Engine site. I add the following commands to finalize_update task.
run "cp #{shared_path}/.htaccess #{release_path}/."
run "cp #{shared_path}/database.php #{release_path}/system/expressionengine/config/."
run "cp #{shared_path}/config.php #{release_path}/system/expressionengine/config/."
NOTE: You should replace system with whatever you used for your own expression engine install.
I don’t like to version the environment specific files, those that differ depending on what server they’re on. For expression engine thats the files .htaccess, database.php and config.php. I leave these files in shared directory we created at the start of the tutorial and copy them to the correct location during the deployment process.
Because capistrano creates a fresh new site each time you deploy the site you need to make sure any uploaded content is kept in the shared directory. You do this by adding a symlink to the image directories.
run "ln -s #{shared_path}/uploads #{release_path}/images/uploads"
run "ln -s #{shared_path}/sized #{release_path}/images/sized"
run "ln -s #{shared_path}/captchas #{release_path}/images/captchas"
So heres the final script, I know setting all this is a long winded process just to deploy a website but it is highly recommended. It means you don’t need to know anything about the site to deploy it, you just need to use cap deploy. Also if you need to deploy to more than one server it is a breeze to include.
The final deploy.rb
set :application, "example.co.uk"
set :repository, "svn+ssh://svn-repository.co.uk/example"
# If you have previously been relying upon the code to start, stop
# and restart your mongrel application, or if you rely on the database
# migration code, please uncomment the lines you require below
# If you are deploying a rails app you probably need these:
# load 'ext/rails-database-migrations.rb'
# load 'ext/rails-shared-directories.rb'
# There are also new utility libaries shipped with the core these
# include the following, please see individual files for more
# documentation, or run `cap -vT` with the following lines commented
# out to see what they make available.
# load 'ext/spinner.rb' # Designed for use with script/spin
# load 'ext/passenger-mod-rails.rb' # Restart task for use with mod_rails
# load 'ext/web-disable-enable.rb' # Gives you web:disable and web:enable
# If you aren't deploying to /u/apps/#{application} on the target
# servers (which is the default), you can specify the actual location
# via the :deploy_to variable:
set :deploy_to, "/var/www/vhosts/www.example.co.uk/"
# If you aren't using Subversion to manage your source code, specify
# your SCM below:
# set :scm, :subversion
# see a full list by running "gem contents capistrano | grep 'scm/'"
role :web, "www.example.co.uk"
set :use_sudo, false
namespace :deploy do
desc 'Complete final actions before switching over symlink'
task :finalize_update do
run "chmod -R g+w #{release_path}"
run "cp #{shared_path}/.htaccess #{release_path}/."
run "cp #{shared_path}/database.php #{release_path}/system/expressionengine/config/."
run "cp #{shared_path}/config.php #{release_path}/system/expressionengine/config/."
run "ln -s #{shared_path}/uploads #{release_path}/images/uploads"
run "ln -s #{shared_path}/sized #{release_path}/images/sized"
run "ln -s #{shared_path}/captchas #{release_path}/images/captchas"
end
desc 'Overrides default action to restart the server - not needed for Expression Engine site'
task :restart do
end
end