Deploying to Elastic Beanstalk using symlinks to avoid checking database.yml into git Deploying to Elastic Beanstalk using symlinks to avoid checking database.yml into git

July 26, 2014   |   Tech
Agile League

If you follow the standard instructions for deploying to Beanstalk, they’ll tell you to check your database.yml file into git. On the plus side, they recommend using environment variables for storing your production settings, but on the minus side, it’s still a pain to commit this file. Every developer is going to have their own settings for database names, usernames, and passwords. As soon as one person checks in database.yml, everyone else will get conflicts and generally have to futz with it to get it working again. I’m sure you know what I’m talking about.
The solution for Beanstalk is to use an (undocumented) pre-deploy hook to symlink the database.yml during deployment. Here’s how to do it:

Update the Beanstalk Configuration

From the Beanstalk management console, set the environment variables for the database. Most likely you’ve already done this if you’re following the standard Beanstalk setup up to this point.

Get Your Files Organized

Copy all your database settings to database.yml.example:

common: &common
  adapter: mysql2
  encoding: utf8
  pool: 5
local_credentials: &local_credentials
  username: "localusername"
  password: "localpassword"
beanstalk_credentials: &beanstalk_credentials
  host: <%= ENV['RDS_HOSTNAME'] %>
  database: <%= ENV['RDS_DB_NAME'] %>
  username: <%= ENV['RDS_USERNAME'] %>
  password: <%= ENV['RDS_PASSWORD'] %>
  database: myapp_development

Pull your database.yml out of git, update .gitignore, and copy the database.yml.example to database.yml

git rm config/database.yml
echo "config/database.yml" >> .gitignore
cp config/database.yml.example config/database.yml
# Edit the database.yml to insert your actual local settings

The Secret Pre-Deploy Hook

Put the following file in .ebextensions/01rails.config The filename doesn’t matter, but the path does.

# Symlink the ondeck database.yml to database.yml.example
    mode: "000777"
    content: |
      cd /var/app/ondeck/config
      ln -sf database.yml.example database.yml

What Does It Do?

The secret here is the name of the file created in the hook (01a_symlink_database_yml). Based on the lexicographic ordering, this will get run after 01_unzip (which unzips your project into /var/app/ondeck) but before anything else, namely asset compilation or database migrations. The commands symlink the database.yml to database.yml.example, which already has all your settings prepared for production using environment variables. The symlink itself has no path information, just pointing to a file in the same directory, so it still functions after /ondeck is renamed to /current.
Unfortunately, as I’m sure you’ve guessed, there are some downsides to this. So far, I’ve identified the following:

  1. It uses the built-in appdeploy hooks, which are undocumented and not mentioned anywhere in the Beanstalk docs. By now you’re probably used to using them anyway, but just realize that it could go away or change without notice.
  2. It uses hardcoded paths instead of the EB_CONFIG_XYZ environment variables that Beanstalk provides. Unfortunately, they’re not set yet when this script runs. These hardcoded paths have changed before (ie. “support” became “containerfiles”) so don’t be surprised when they change again. The most likely breaking-change would be “ondeck” gets renamed.

I hope this helps someone else out. I tried for a long time to get it to work with commands and/or container_commands, but the ordering of the deploy steps is quite rigid (and stupid). It’s pretty much impossible to manage files in your application (copy, move, symlink) after the code is deployed but before asset compilation and database migration run, which expect your Rails environment to be fully functional.
The more I use Beanstalk, the more I like Heroku. If you have the authority to make the call about your hosting stack, read my review of Elastic Beanstalk and ask yourself if you would be better served by a different hosting platform.