Deploying to AWS EC2 using Capistrano

This guide walks through the process of creating a cloud server using Amazon Web Services, configuring it to run as a deployment server and setting up a deployment workflow using Capistrano

1. Create and login to a new EC2 instance

  • Create an AWS account (you will be eligible for one year free-tier). Navigate to the EC2 Dashboard in Singapore region

    aws_capistrano1

  • In local machine Terminal enter the following command and copy the result. If you do not have a default SSH key, follow the steps here to generate one with the identity id_rsa

      cat ~/.ssh/id_rsa.pub
    
  • From the AWS EC2 dashboard click ‘Key Pairs’, then ‘Import Key Pairs’. Specify the name id_rsa and paste the key you just copied from your local machine. Then click ‘Import’.

  • Click on Instances in the sidebar and 'Launch Instance'. Select Ubuntu Server 14.04 LTS (HVM), SSD Volume Type - ami-25c00c46

    aws_capistrano2

  • Select General purpose: t2.micro and click 'Review and Launch'

    aws_capistrano3

  • At the review page, click 'Edit Security Groups'.

    aws_capistrano4

  • Create a security group named merchant and Add Rule HTTP to allow incoming HTTP connections on port 80 (see image):

    aws_capistrano_security

  • Click 'Review and Launch' to go back to the review page. Click 'Launch'

  • To associate an SSH key to enable secure remote login, select 'Choose an existing key pair' and select id_rsa

    aws_capistrano_launch

  • Check the check-box and click 'Launch Instances'. You will be brought to the post-launch page

    aws_capistrano_launched

  • Click on View Instances to be taken back to the AWS EC2 console to view your new instance. After a short initialization time (about 30 seconds), the instance should have passed status checks

    aws_capistrano_dashboard

  • When Capistrano is run on the local machine, it will connect to your EC2 server via SSH to remotely pull down the latest code from GitHub and deploy it into production. To enable this, we need to allow the local machine to forward SSH requests

  • On local machine, open the file ~/.ssh/config and add the following (NOTE: replace the IP address with that of your EC2 instance)

      Host 52.77.234.2
       ForwardAgent yes
    
  • Next, you need to add your local private key to the ssh-agent - reference here

      mhan@Mingdings-MacBook-Air:~$ eval "$(ssh-agent -s)"
      Agent pid 29576
    
      mhan@Mingdings-MacBook-Air:~$ ssh-add ~/.ssh/id_rsa
      Identity added: /Users/mhan/.ssh/id_rsa (/Users/mhan/.ssh/id_rsa)
    
  • Login to your EC2 instance ssh -i ~/.ssh/id_rsa [email protected] (note the IP address). You should see the following:

      The authenticity of host '52.77.234.2 (52.77.234.2)' can't be established.
      RSA key fingerprint is fc:ed:63:62:dc:48:e7:a8:c7:0e:20:75:3f:48:76:55.
      Are you sure you want to continue connecting (yes/no)? yes
      Warning: Permanently added '52.77.234.2' (RSA) to the list of known hosts.
      Welcome to Ubuntu 14.04.3 LTS (GNU/Linux 3.13.0-74-generic x86_64)
    
       * Documentation:  https://help.ubuntu.com/
    
        System information as of Sat Feb 20 11:21:36 UTC 2016
    
        System load: 0.16             Memory usage: 5%   Processes:       81
        Usage of /:  9.9% of 7.74GB   Swap usage:   0%   Users logged in: 0
    
        Graph this data and manage this system at:
          https://landscape.canonical.com/
    
        Get cloud support with Ubuntu Advantage Cloud Guest:
          http://www.ubuntu.com/business/services/cloud
    
      0 packages can be updated.
      0 updates are security updates.
    
    The programs included with the Ubuntu system are free software;
    the exact distribution terms for each program are described in the
    individual files in /usr/share/doc/*/copyright.

    Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
    applicable law.

    ubuntu@ip-172-30-0-195:~$ 

2. Setup EC2 instance with Ruby and RVM

Step by step instructions to setup your EC2 server for deploying a Rails application. Reference here

  • Login to your EC2 instance

      ubuntu@ip-172-30-0-195:~$ pwd
      /home/ubuntu
    
  • Switch to root user privileges to update system and install Ubuntu packages

      # from ubuntu@ip-172-30-0-195:~$
      sudo -s
    
      # from root@ip-172-30-0-195:~#
      apt-get update
      apt-get -y upgrade
      apt-get -y install build-essential libmagickcore-dev imagemagick libmagickwand-dev libxml2-dev libxslt1-dev git-core nginx redis-server curl nodejs htop
    
      # Set Rails environment
      echo "RAILS_ENV=production" >> /etc/environment
    
      # Add a gemrc file to ubuntu user
      echo -e "verbose: true\nbulk_threshold: 1000\ninstall: --no-ri --no-rdoc --env-shebang\nupdate: --no-ri --no-rdoc --env-shebang" > /home/ubuntu/.gemrc
      chmod 644 /home/ubuntu/.gemrc
      chown ubuntu:ubuntu /home/ubuntu/.gemrc
      exit
      ubuntu@ip-172-30-0-195:~$ 
    
  • Install RVM and Ruby - reference here

      \curl -sSL https://get.rvm.io | bash -s
      source ~/.rvm/scripts/rvm
    
      rvm install 2.2.2
      rvm use 2.2.2 --default
      # Using /home/ubuntu/.rvm/gems/ruby-2.2.2
    
      ruby -v
      # ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
    
      gem install bundler
      # Fetching: bundler-1.11.2.gem (100%)
      # Successfully installed bundler-1.11.2
      # 1 gem installed
    

3. Install and configure Nginx, Passenger and PostGreSQL

  • Switch to root user privileges

      # from ubuntu@ip-172-30-0-195:~$
      sudo -s
    
  • Install Git + Passenger + Nginx

      apt-get install -y git
    
      apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
      apt-get install -y apt-transport-https ca-certificates
      sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger trusty main > /etc/apt/sources.list.d/passenger.list'
    
      # update ubuntu sources
      apt-get update
      apt-get install -y nginx-extras passenger
    
  • Enable the Passenger Nginx module

      vi /etc/nginx/nginx.conf
    
      # uncomment the following lines
      # passenger_root /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini;
      # passenger_ruby /usr/bin/passenger_free_ruby;
    
  • Restart Nginx and validate the installation

      service nginx restart
      passenger-config validate-install
    
      # * Checking whether this Passenger install is in PATH... ✓
      # * Checking whether there are no other Passenger installations... ✓
    
      # Everything looks good. :-)
      exit
      ubuntu@ip-172-30-0-195:~$ 
    
  • To ensure Nginx knows where to find the application code after we deploy, create a new conf file in /etc/nginx/sites-enabled with the application name merchant

      cd /etc/nginx/sites-enabled
      sudo vi merchant.conf
    
  • Add the following Nginx server configuration

      server {
        listen 80;
        server_name 52.77.234.2; #replace with your server ip or domain
    
        # specify the application's 'public' directory 
        root /home/ubuntu/apps/merchant/current/public;
    
        # Turn on Passenger
        passenger_enabled on;
        passenger_ruby /home/ubuntu/.rvm/gems/ruby-2.2.2/wrappers/ruby;
      }
    
  • As mentioned in the reference, the last line passenger_ruby should be replaced by the path to the correct Ruby interpreter to use passenger-config about ruby-command

  • Restart Nginx

      sudo service nginx restart
    
  • Setting Up PostgreSQL

      sh -c "echo 'deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main' > /etc/apt/sources.list.d/pgdg.list"
      wget --quiet -O - http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | sudo apt-key add -
    
      sudo apt-get update
      sudo apt-get install -y postgresql-common
      sudo apt-get install -y postgresql-9.3 libpq-dev
    
  • Test that pg gem can install properly with native extensions

      gem install pg -v '0.18.3'
    
      # Fetching: pg-0.18.3.gem (100%)
      # Building native extensions.  This could take a while...
      # Successfully installed pg-0.18.3
      # 1 gem installed
    
  • Configure PostgreSQL to accept all connections

      ls /etc/postgresql
      sudo vim /etc/postgresql/9.3/main/pg_hba.conf 
    
      # change the following lines to METHOD 'trust', and save
      # =======================
      # TYPE  DATABASE        USER            ADDRESS                 METHOD
    
      # "local" is for Unix domain socket connections only
      # local   all             all                                     trust
      # IPv4 local connections:
      # host    all             all             127.0.0.1/32            trust
      # IPv6 local connections:
      # host    all             all             ::1/128                 trust
    
      sudo service postgresql reload
      sudo -u postgres createuser ubuntu -s
    

4. Setup Capistrano on local machine

  • Switch to a new code branch: git checkout -b capistrano

  • Update Gemfile for development environment and bundle install

      group :development do
        gem 'capistrano', '~> 3.4'  
        gem 'capistrano-rails', '~> 1.1'
        gem 'capistrano-bundler'
        gem 'capistrano-passenger'
        gem 'capistrano-rvm'
      end
    
      # Capistrano 3.1 has some breaking changes. Please check the CHANGELOG: http://goo.gl/SxB0lr
      # If you're upgrading Capistrano from 2.x, we recommend to read the upgrade guide: http://goo.gl/4536kB
      # The `deploy:restart` hook for passenger applications is now in a separate gem called capistrano-passenger.  Just add it to your Gemfile and require it in your Capfile.
    
  • Enable your project to use Capistrano

      bundle exec cap install STAGES=production
    
      # mkdir -p config/deploy
      # create config/deploy.rb
      # create config/deploy/production.rb
      # mkdir -p lib/capistrano/tasks
      # create Capfile
      # Capified
    
  • In the Capfile uncomment these lines:

      # Capfile
      require 'capistrano/rvm'
      require 'capistrano/bundler'
      require 'capistrano/rails/assets'
      require 'capistrano/rails/migrations'
      require 'capistrano/passenger'
    
  • Specify Capistrano configuration for deployment instance

      # config/deploy.rb
      set :application, 'merchant'
      set :repo_url, '[email protected]:hanmd82/merchant.git'
      ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp
      set :deploy_to, '/home/ubuntu/apps/merchant'
      set :scm, :git
      set :format, :pretty
      set :log_level, :info
      set :pty, true
      set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')
      set :keep_releases, 5
    
  • Configure Capistrano to get app repo from Github and deploy

      # config/deploy/production.rb
      server '52.77.234.2', user: 'ubuntu', roles: %w{web app db}
      set :ssh_options, { forward_agent: true }
    

5. Deploy the app with Capistrano

  • Ensure that these two gems are in your Gemfile and bundle install

      gem 'therubyracer', platforms: :ruby
      gem 'execjs'
    
  • Export IP address of EC2 instance as local environment variable

      export PRODUCTION_SERVER_IP_ADDR=54.169.70.160
    
  • Deploy from local machine with Capistrano

      cap production deploy
    
  • Create production database if you see the following error ActiveRecord::NoDatabaseError: FATAL: database "merchant_app_production" does not exist

      # from ubuntu@ip-172-30-0-195:~$
      cd ~/apps/merchant/releases/${CURRENT_VERSION}
      RACK_ENV=production rake db:create
    
  • SSH into the EC2 instance to generate and copy new secret key

      # from ubuntu@ip-172-30-0-195:~$
      cd ~/apps/merchant/current
      bundle exec rake secret
    
  • Set the newly generated secret key as an environment variable using sudo vi ~/.profile

      export SECRET_KEY_BASE=${NEW_SECRET_KEY}
    
  • Check that you can access your application server

      curl 52.77.234.2 # replace with your IP address or domain
      # you should see HTML markup corresponding to your index/html
    
  • Run database seeding tasks (if any)

      # ubuntu@ip-172-30-0-195:~/apps/merchant/current$ 
      RACK_ENV=production bundle exec rake db:seed
    
  • Navigate to http://52.77.234.2 in a web browser window to view your application