Deploying a CakePHP site with Capistrano

In the past I’ve used a variety of tools to deploy client sites, most often using version control. However, for my blog I’ve always used FTP. Its a pretty old-school approach, and something that I’ve been lacking the time to correct. Last weekend I finally took the plunge and figured out how to get Capistrano to deploy my site. Because of my hosting setup I wasn’t able to use capcake. But if you have git installed on your server, its worth taking a look at.

Installing Capistrano & getting ready

This was the easiest part of the process. Just run gem install capistrano and you’re done. I then capify’ied my site by running capify from the app root. I then spent a while getting familiar with how Capistrano actually worked and evaluating how I would have to change my current deployment to work better with Capistrano. Before using Capistrano my directory layout looked like.

Show Plain Text
  1. /home/mark/public_html/app
  2. /home/mark/public_html/app/webroot
  3. /home/mark/public_html/cake
  4. /home/mark/public_html/vendors

Far from the best setup as everything is inside the DocumentRoot. Fixing the files exposed to the outside world was another thing I wanted to address while changing how I deploy my site. I initially configured Capistrano to make the following directories.

Show Plain Text
  1. /home/mark/ # The last 5 releases
  2. /home/mark/  # The current release
  3. /home/mark/   # Files shared among

I would then symlink ~/public_html to ~/ This would minimize the files exposed to the outside world, and work with how Capistrano likes to do things.

Other bits that needed wiring

Configuration and uploaded files posed a small hurdle, I solved both with symlinks. Configuration files were put in ~/, and symlinked in on deploy. Uploaded files would follow the same pattern. After creating ~/ to hold the files, I used more symlinks to wire things together. I ended up putting my cakephp core and plugins for my site into ~/ as well. This resulted in a directory structure that looked like:

Show Plain Text
  1. /home/mark/
  2. /home/mark/
  3. /home/mark/
  4. /home/mark/
  5. /home/mark/
  6. /home/mark/

Getting all the directories and symlink wiring setup was an iterative process, I made lots of mistakes along the way. With some help from savant on IRC, and stealing some functions from the previously mentioned capcake, I ended up with the following deploy.rb

Show Plain Text
  1. set :application, ""
  2. set :repository,  "/Users/markstory/Sites/mark_story/site/"
  3. set :branch, "master"
  4. set :scm, :git
  6. # Deploy settings
  7. set :deploy_to, "/home/mark/#{application}"
  8. set :deploy_via, :copy
  10. set :copy_exclude, [".git/*", ".gitignore"]
  11. set :copy_compression, :gzip
  13. # Use account tmp dir as /tmp is wierd.
  14. set :copy_remote_dir, '/home/mark/tmp'
  16. # Configure which plugins are going to be updated when the code is deployed.
  17. set :plugins_dir, "/Users/markstory/Sites/cake_plugins"
  18. set :app_plugins, ['asset_compress']
  20. # Options
  21. set :use_sudo, false
  22. set :keep_releases, 5
  24. # Roles & servers
  25. role :app, ""
  27. server '', :app, :primary => true
  28. set :user, 'mark'
  30. # Environments
  31. task :production do
  32.   set :deploy_to, '/home/mark/'
  33. end
  35. # Deployment tasks
  36. namespace :deploy do
  37.   desc "Override the original :restart"
  38.   task :restart, :roles => :app do
  39.     clear_cache
  40.   end
  42.   task :finalize_update, :roles => :app do
  43.     # Link cakephp. Not the ideal linking but it works.
  44.     run "ln -s #{shared_path}/cakephp #{current_release}/cake"
  46.     # Link configuration files
  47.     run "ln -s #{shared_path}/config/core.php #{current_release}/config/core.php"
  48.     run "ln -s #{shared_path}/config/database.php #{current_release}/config/database.php"
  50.     # Link uploaded files.
  51.     run "rm -rf #{current_release}/webroot/img/downloads;
  52.         ln -s #{shared_path}/webroot/downloads #{current_release}/webroot/img/downloads"
  54.     run "rm -rf #{current_release}/webroot/img/portfolio;
  55.         ln -s #{shared_path}/webroot/portfolio #{current_release}/webroot/img/portfolio"
  57.     run "ln -s #{shared_path}/webroot/files #{current_release}/webroot/files"
  58.     run "ln -s #{shared_path}/webroot/demos #{current_release}/webroot/demos"
  60.     # Link tmp
  61.     run "rm -rf #{current_release}/tmp"
  62.     run "ln -s #{shared_path}/tmp #{current_release}/tmp"
  64.     # Link plugins
  65.     deploy.plugins.symlink
  66.   end
  68.   namespace :plugins do
  69.     desc "Symlinks the configured plugins for the appliction into plugins, from the shared dirs."
  70.     task :symlink, :roles => :app do
  71.       app_plugins.each { |plugin|
  72.         run "ln -s #{shared_path}/plugins/#{plugin} #{latest_release}/plugins/#{plugin}"
  73.       }
  74.     end
  75.   end
  77.   namespace :web do
  78.     desc "Setup lock file"
  79.     task :disable, :roles => :app do
  80.         run "touch #{current_release}/webroot/.capistrano-lock"
  81.     end
  83.     desc "Enable the current access after deployment"
  84.     task :enable, :roles => :app do
  85.       run "rm #{current_release}/webroot/.capistrano-lock"
  86.     end
  87.   end
  88. end
  90. namespace :clear_cache do
  91.   desc <<-DESC
  92.     Blow up all the cache files CakePHP uses, ensuring a clean restart.
  93.   DESC
  94.   task :default do
  95.     # Remove absolutely everything from TMP
  96.     run "rm -rf #{shared_path}/tmp/*"
  98.     # Create TMP folders
  99.     run "mkdir -p #{shared_path}/tmp/{cache/{models,persistent,views},sessions,logs,tests}"
  100.   end
  101. end
  103. namespace :pending do
  104.   desc <<-DESC
  105.     Displays the 'diff' since your last deploy. This is useful if you want \
  106.     to examine what changes are about to be deployed. Note that this might \
  107.     not be supported on all SCM's.
  108.  DESC
  109.  task :diff, :except => { :no_release => true } do
  110.    system(source.local.diff(current_revision))
  111.  end
  113.  desc <<-DESC
  114.    Displays the commits since your last deploy. This is good for a summary \
  115.    of the changes that have occurred since the last deploy. Note that this \
  116.    might not be supported on all SCM's.
  117.   DESC
  118.   task :default, :except => { :no_release => true } do
  119.     from = source.next_revision(current_revision)
  120.     system(source.local.log(from))
  121.   end
  122. end

I have to say that after setting up Capistrano, I’m super happy with how it works. I love being able to use one command to update my site and not have to worry about accidentally breaking something due to fat fingering something in my FTP client. While I still need to update Cake core and my plugins manually I do that pretty rarely so it shouldn’t pose a problem.


Does the clear_cache task actually work? mkdir -p never works for me when doing it through capistrano, although that may be because I am using Capistrano multi-environment.

Nice to see you got everything settled, let me know if you need any more help with Capistrano.

Jose Diaz-Gonzalez on 5/30/10

Hi, I made a railsless deploy extension for just this reason: –– the file replaces the deploy.rb in the main gem, with one with the tasks removed from the chain, not simple stubbed out as above… hopefully it provides whatever you need :)

Lee Hambley on 6/1/10

What do you gain with using Capistrano in deployment.

I keep a Git bare repo with my app which I push from my to from my macbook. Then I clone this .bare repo in a regular repo on the same server in my apps/ directory. For the /css and /img folder I just make a symlink in web_root and add a custom index.php also in the web_root with the right path to Cake and my app.

After I push new commits to the Git bare rep I log in via ssh and pull these commits into my live application.

Setting this up: 20-30 min
Updating: in no time..

Can Capistrano do it better?

Gerhard Sletten on 6/5/10


I type cap deploy and my code is deployed. I don’t need to ssh into the box, cd to the folder, type “git pull origin master” etc.

Multi-environment is easy. cap dev deploy and cap prod deploy is how I deploy

In Mark Story’s case, he had no access to git on the server, so using your method is out of the question. Capistrano saves the day.

Jose Diaz-Gonzalez on 6/5/10

Mark, did you end up having problems with getting files created under current/app/tmp/cache?

I’ve got a basic setup that currently uses file cache, but CakePHP (1.3.2) ends up looking for the tmp/cache under releases because of the definition of APP_DIR. Because I have softlinked tmp from shared to current, similar to what you have done, current/tmp/cache is never found, because CakePHP looks for it under releases/2010…/app/tmp/cache. (I think I said the same thing twice in a row)

Reuben Helms

Reuben Helms on 6/16/10

Gah, don’t mind me. I had linked tmp to /tmp instead of /app/tmp.

Reuben Helms on 6/16/10

Hello Mark,

i have created an own deployment shell for CakePHP, because i don’t feel good with mixing php and ruby ;)

I think an integrated CakePHP deployment shell could be a nice think to the comunity, could’n it?

details can find here:

sassman on 7/16/10

What is inside your releases & current folder.

Friskd on 12/2/10

Comments are not open at this time.