Capistrano: File Transfer actions

Capistrano provides 5 super helpful methods you can use to transfer files and resources from your local machine to remote servers and vice versa. Those methods lives in a module called FileTransfer, part of the Capistrano::Configuration::Actions namespace.

The FileTransfer methods are:

These methods are not a merely a ssh-based implementation of the basic FTP commands. Because they lives in the Capistrano and Rails universe, they know everything about your environment including your deployment roles, servers and connection gateways.

And this is exactly the case when those methods turn out to be really useful. Suppose your production servers are reachable only through a gateway. Unless your FTP client supports a gateway-based connection, you would need to authenticate to the gateway server and then authenticate from the gateway to the end-point.

Because Capistrano file transfer methods know about your deployment settings, you don't need to bother with authentication or ssh connections. These methods will dutifully obey your deploy.rb configurations and use them.

Let me show you some examples.

upload(from, to, options={}, &block)

The upload action stores the file at the given path on all servers targeted by the current task.

If you ever used the deploy:upload task before, then you might already know how this method works. It takes the path of the resource you want to upload and the target path on the remote servers.

desc "Uploads CHANGELOG.txt to all remote servers."
task :upload_changelog do
  upload("#{RAILS_ROOT}/CHANGELOG.txt", "#{current_path}/public/CHANGELOG")
end

Beware that source and target path aren't automatically scoped to your Rails project. That said, if you forget to prepend the current_path variable to the target path, Capistrano will try to upload your CHANGELOG.txt file to the /public folder on your server instead of the /public folder under your Rails instance.

Unless otherwise specified, the upload method copies the source content to the target path on any remote server. If you want to alter the default behavior you need to pass a role or a group of servers as option.

upload source, target, :hosts => "192.168.0.10"
upload source, target, :hosts => %w(192.168.0.10 192.168.0.11)
upload source, target, :only => { :primary => true }

put(data, path, options={})

The put action stores the data at the given path on all servers targeted by the current task. This action is built on the top of upload. Compared with upload, this action requires data to be a data string (ex. an IO) instead of a file system path.

download(from, to, options={}, &block)

As you can guess, the download action is the opposite of upload. It downloads the from file from all configured servers and transfers them to your local machine.

As for upload, you can selectively download the file from specific locations passing either a role or a hosts option. If you need to download the file once without selectively specify the source, check out the get method below.

get(remote_path, path, options={}, &block)

The get action downloads the file remote_path from the FIRST configured server and transfers it to your local machine.

It is built on the top of the download method. The main difference between the get and the download action described above is that the former downloads remote_path from the FIRST server encountered in the server chain.

Let me show you a real world example. I have a multi-language Rails application where I installed the Translate plugin. For security reason, the translation interface is available in a staging environment only, restricted from public access. Just before a new release is shipped on production, I need to download the translations from staging, commit the changes to the repository and finally deploy the release.

Both production and staging servers are not directly accessible. They are part of a server cluster and the only way to ssh them is to ssh a gateway server, then open a ssh connection from the gateway to the target server. As you probably understand, downloading files from those servers to my local computer is a major hassle.

Fortunately, my deploy.rb file already includes all the configuration I need to connect to the staging server from my local machine through the right gateway.

set :gateway_username,  fetch(:gateway_username, default_gateway_username)
set :gateway, "#{gateway_username}@zeus"
set :host, fetch(:host, "apollo")

role :app, fetch(:host)
role :web, fetch(:host)
role :db,  fetch(:host), :primary => true # set to false to not migrate

Great! All what I need is to create a task to download each language file from the remote server.

namespace :i18n do

  desc <<-EOD
    Downloads all translation files (except the English one) from the server 
    and stores them into the local config/locales folder.
  EOD
  task :download_translations do
    Language.all.each do |language|
      next if language.locale == :en
      get "#{current_path}/config/locales/#{language.locale}.yml", "config/locales/#{language.locale}.yml"
    end
  end

end

Once more, Capistrano saved me hundreds of keystrokes!

transfer(direction, from, to, options={}, &block)

The transfer action is the lowest-level method available. Under the hood, all methods above rely on transfer.

transfer requires a direction, the source path and the target path. Supported directions are :up (for uploads) and :down (for downloads).

The following example demonstrates how to upload a file to all remote servers using the transfer action.

transfer :up, "#{RAILS_ROOT}/CHANGELOG.txt", "#{current_path}/public/CHANGELOG"

As you probably guess, this is equivalent to the following example used to describe the upload action (for the sake of completeness, upload also provides the ability to chmod the file once stored on the remote server).

upload("#{RAILS_ROOT}/CHANGELOG.txt", "#{current_path}/public/CHANGELOG")

Unless you need to implement a custom transfer mechanism, I suggest you to use one of the previous methods.