Deploying with Capistrano When You Can't Access Subversion

Problem

Contributed by: Ben Bleything

You want to use Capistrano to deploy your Rails application, but your deployment server cannot access your Subversion repository. This recipe is also useful if you use a source control system that Capistrano does not natively support.

Solution

Capistrano's update_code task is the code responsible for getting the new version of your code onto the server. Override it in config/deploy.rb like so:

config/deploy.rb:

# Your deploy.rb contents here task :update_code, :roles => [:app, :db, :web] do
 on_rollback { delete release_path, :recursive => true }
 # this directory will store our local copy of the code
 temp_dest = "to_deploy"
 # the name of our code tarball
 tgz = "to_deploy.tgz"
 # export the current code into the above directory
 system("svn export -q #{configuration.repository} #{temp_dest}")
 # create a tarball and send it to the server
 system("tar -C #{temp_dest} -czf #{tgz} .")
 put(File.read(tgz), tgz)
 # untar the code on the server
 run <<-CMD
 mkdir -p #{release_path} &&
 tar -C #{release_path} -xzf #{tgz} 
 CMD
 # symlink the shared paths into our release directory
 run <<-CMD
 rm -rf #{release_path}/log #{release_path}/public/system &&
 ln -nfs #{shared_path}/log #{release_path}/log &&
 ln -nfs #{shared_path}/system #{release_path}/public/system
 CMD
 # clean up our archives
 run "rm -f #{tgz}"
 system("rm -rf #{temp_dest} #{tgz}")
end

With that method changed, you can now deploy like normal:

$ cap deploy

Discussion

To deploy your code when you can't check it out directly, you need to find another way to get the code to the server. The simplest method is to make an archive of the code, as demonstrated earlier. By doing so, you get to take advantage of Capistrano's built-in handling of multiple servers.

You can also alter the solution for situations when your application is not in source control:

# Your deploy.rb contents here task :update_code, :roles => [:app, :db, :web] do
 on_rollback { delete release_path, :recursive => true }
 # the name of our code tarball
 tgz = "to_deploy.tgz"
 # create a tarball and send it to the server
 system("tar -czf /tmp/#{tgz} .")
 put(File.read("/tmp/#{tgz}"), tgz)
 # untar the code on the server
 run <<-CMD
 mkdir -p #{release_path} &&
 tar -C #{release_path} -xzf #{tgz} 
 CMD
 # symlink the shared paths into our release directory
 run <<-CMD
 rm -rf #{release_path}/log #{release_path}/public/system &&
 ln -nfs #{shared_path}/log #{release_path}/log &&
 ln -nfs #{shared_path}/system #{release_path}/public/system
 CMD
 # clean up our archives
 run "rm -f #{tgz}"
 system "rm -f /tmp/#{tgz}"
end

The main difference here is that we're now taking a tarball of the current directory and uploading that, instead of exporting a fresh copy to a temporary directory.

It would also be possible to use scp, sftp, rsync, or any number of other file transfer methods, but each has its disadvantages. One of Capistrano's strong points is that it executes commands on clusters of servers at once. The alternatives mentioned earlier all share one major disadvantage: there's no good way to let Capistrano do the heavy lifting for you. If you have only one server, this is less of a problem, but in a multiserver environment, using methods other than the solution above will quickly get unwieldy.

For example, to use scp, you would either need to iterate over the servers you have defined, pushing the code to each in turn, or use Capistrano's run method to invoke scp on the remote server to pull the code to the server from your local workstation. The latter method has other difficulties, too, not least of which is setting up the deployment server with an SSH key to access your workstation and getting that key into the session that Capistrano is running.

See Also