Effective Rails Deployment

Capistrano has been an excellent tool for years to deploy simple or multi-stage complex applications to remote servers. However the rise of PASS like Heroku, Dokku, I got in mind to use some automatic orchestration framework.

I looked over Chef, Puppet, SaltStack and Ansible on a weekend and decided to go with ansible since it looked easiest to get hands dirty and have great documentation. After some initial hicups I was able to deploy some of our inhouse applications on EC2 and introduced it to my coworkers at codecrux.

Since then, we use ansible to configure and manage remote server, and also configure our local development environment running on top of Virtualbox with vagrant.

Ansible uses YAML for configuration and for actual commands it has less flexibility compared to capistrano which uses Ruby DSL, but at the same time helps you to keep your playbooks simple. ansible also extremely good with orchestration and rolling deployments. We were able to keep a homogenous command set and duplicate most of Capistrano’s features in very small amount of code.

Let’s take an example to deploy lobster.rs. Here is how we did it -

Configuration

First of all you need to define required variables in a yaml file for ansible to pick them up.

  app_name: lobsters
  rails_env: production

  git_url: git@github.com:jcs/lobsters.git
  git_version: master

  app_path: '/'
  shared_path: '/shared'
  releases_path: '/releases'
  current_release_path: '/current'
  app_public_path: "/public"
  app_config_path: "/config"
  app_temp_path: "/tmp"
  app_logs_path: "/log"

  keep_releases: 5

You should also define your remote host in ansible inventory/host file like

[production]
lobsters.dev   ## replace with your domain name

Playbook

Now when we have configuration we can create the actual playbook for doing capistrano-style deployments. Create deploy.yml file:

---
- hosts: all
  tasks:
    - set_fact: this_release_ts=
    - set_fact: this_release_path=/

    - debug: msg='New release path '

    - name: Create new release dir
      file: path= state=directory

    - name: Update code
      git: repo= dest= version= accept_hostkey=yes
      register: git

    - debug: msg='Updated repo from  to '

    - name: Symlink shared files
      file: src=/ dest=/ state=link force=yes
      with_items:
        - config/database.yml
        - config/secrets.yml
        - config/unicorn.rb
        - log
        - tmp
        - vendor/bundle

    - name: Install bundle
      command: 'bundle install --deployment --without="development test"'
      args:
        chdir: ''

    - name: Precompile assets
      command: rake assets:precompile chdir=
      environment:
        RAILS_ENV: ''

    - name: Migrate database
      command: rake db:migrate chdir=
      environment:
        RAILS_ENV: ''

    - name: Symlink new release
      file: src= dest= state=link force=yes

    - name: Restart unicorn
      command: sudo restart 

    - name: Cleanup
      shell: "ls -1t |tail -n +|xargs rm -rf"
      args:
        chdir: ''

Deploy

Once everything is set up, you can run the following command to deploy:

ansible-playbook -u railsbox -i production deploy.yml