# Capistrano::S3::Copy This is a revised implementation of the ideas in Bill Kirtleys capistrano-s3 gem. I have a requirement to push new deployments via capistrano, but also to retain the last deployed package in S3 for the purposes of auto-scaling. This gem use Capistrano's own code to package the tarball, but instead of deploying it to each machine, we deploy it to a configured S3 bucket (using s3cmd provided by the rahugo-s3sync gem https://github.com/frahugo/s3sync), then deploy it from there to the known nodes from the capistrano script. ## Installation Add these line to your application's Gemfile: group :development do gem 'capstrano-s3-copy' end And then execute: $ bundle Or install it yourself as: $ gem install capistrano-s3-copy ## Usage In your deploy.rb file, we need to tell Capistrano to adopt our new strategy: set :deploy_via, :s3_copy Then we need to provide AWS account details to authorize the upload/download of our package to S3 set :aws_access_key_id, ENV['AWS_ACCESS_KEY_ID'] set :aws_secret_access_key, ENV['AWS_SECRET_ACCESS_KEY'] Finally, we need to indicate which bucket to store the packages in: set :aws_releases_bucket, 'mybucket-deployments' The package will be stored in S3 prefixed with a rails_env that was set in capistrano: e.g. S3://mybucket-deployment/production/201204212007.tar.gz If the deployment succeeds, another file is written to S3: S3://mybucket-deployment/production/aws_install.sh The intention is that auto-scaled instances started after the deploy could download this well-known script to an AMI, and executing it would bring down the latest tarball, and extract it in a manner similar to someone running: cap deploy:setup cap deploy Of course, everyone has tweaks that they make to the standard capistrano recipe. For this reason, the script thats executed is generated from an ERB template. #!/bin/sh # Auto-scaling capistrano like deployment script Rails3 specific. set -x set -e echo "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" echo "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" if [ "${AWS_ACCESS_KEY_ID}" == "" ]; then echo "Expecting the environment variable AWS_ACCESS_KEY_ID to be set" exit 1 fi if [ "${AWS_SECRET_ACCESS_KEY}" == "" ]; then echo "Expecting the environment variable AWS_SECRET_ACCESS_KEY to be set" exit 2 fi AWS_RELEASES_BUCKET=<%= configuration[:aws_releases_bucket] %> RAILS_ENV=<%= configuration[:rails_env] %> # e.g. production DEPLOY_TO=<%= configuration[:deploy_to] %> # e.g. /u/apps/myapp RELEASES_PATH=<%= configuration[:releases_path] %> # e.g. /u/apps/myapp/releases RELEASE_PATH=<%= configuration[:release_path] %> # e.g. /u/apps/myapp/releases/20120428210958 SHARED_PATH=<%= configuration[:shared_path] %> # e.g. /u/apps/myapp/shared CURRENT_PATH=<%= configuration[:current_path] %> # e.g. /u/apps/myapp/current PACKAGE_NAME=<%= File.basename(filename) %> # e.g. 20120428210958.tar.gz S3_PACKAGE_PATH=${RAILS_ENV}/${PACKAGE_NAME} # e.g. production/20120428210958.tar.gz DOWNLOADED_PACKAGE_PATH=<%= remote_filename %> # e.g. /tmp/20120428210958.tar.gz DECOMPRESS_CMD="<%= decompress(remote_filename).join(" ") %>" # e.g. tar xfz /tmp/20120428210958.tar.gz mkdir -p $DEPLOY_TO mkdir -p $RELEASES_PATH mkdir -p ${SHARED_PATH} mkdir -p ${SHARED_PATH}/system mkdir -p ${SHARED_PATH}/log mkdir -p ${SHARED_PATH}/pids touch ${SHARED_PATH}/log/${RAILS_ENV}.log chmod 0666 ${SHARED_PATH}/log/${RAILS_ENV}.log chmod -R g+w ${DEPLOY_TO} # AFTER: cap deploy:setup # Project specific shared directories # mkdir -p ${SHARED_PATH}/content # mkdir -p ${SHARED_PATH}/uploads # cap deploy:update_code s3cmd get ${AWS_RELEASES_BUCKET}:${S3_PACKAGE_PATH} ${DOWNLOADED_PACKAGE_PATH} 2>&1 cd ${RELEASES_PATH} && ${DECOMPRESS_CMD} && rm ${DOWNLOADED_PACKAGE_PATH} # cap deploy:assets_symlink (Rails 3.x specific) rm -rf ${RELEASE_PATH}/public/assets mkdir -p ${RELEASE_PATH}/public mkdir -p ${DEPLOY_TO}/shared/assets ln -s ${SHARED_PATH}/assets ${RELEASE_PATH}/public/assets # cap deploy:finalize_update chmod -R g+w ${RELEASE_PATH} rm -rf ${RELEASE_PATH}/log rm -rf ${RELEASE_PATH}/public/system rm -rf ${RELEASE_PATH}/tmp/pids mkdir -p ${RELEASE_PATH}/public mkdir -p ${RELEASE_PATH}/tmp ln -s ${SHARED_PATH}/system ${RELEASE_PATH}/public/system ln -s ${SHARED_PATH}/log ${RELEASE_PATH}/log ln -s ${SHARED_PATH}/pids ${RELEASE_PATH}/tmp/pids # AFTER: cap deploy:finalize_update cd ${RELEASE_PATH} bundle install --gemfile ${RELEASE_PATH}/Gemfile --path ${SHARED_PATH}/bundle --deployment --quiet --without development test # AFTER: cap deploy:update_code # cap deploy:assets:precompile cd ${RELEASE_PATH} bundle exec rake RAILS_ENV=${RAILS_ENV} RAILS_GROUPS=assets assets:precompile # Project specific shared symlinking #ln -nfs ${SHARED_PATH}/content ${RELEASE_PATH}/public/content #ln -nfs ${SHARED_PATH}/uploads ${RELEASE_PATH}/public/uploads # cap deploy:create_symlink rm -f ${CURRENT_PATH} ln -s ${RELEASE_PATH} ${CURRENT_PATH} # cap deploy:restart # touch ${CURRENT_PATH}/tmp/restart.txt # AFTER: cap deploy:restart # cd ${CURRENT_PATH};RAILS_ENV=${RAILS_ENV} script/delayed_job restart An alternative ERB script can be configured via something like this: set :aws_install_script, File.read(File.join(File.dirname(__FILE__), "custom_aws_install.sh.erb") ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Added some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request