Capistrano and database.yml

June 3rd, 2009 at 7:06 am • permalink12 comments

Capistrano logoLast week, an user asked the Capistrano mailing list about database password best practices. This reminded me that I never posted here a Capistrano recipe I created almost one year ago to solve exactly this problem.

Which problem?

Imagine you need to deploy a new Rails application. As you probably know, Rails stores all the database configurations in a single file called config/database.yml, including database authentication credentials.

This file usually lives in your repository along with all your application code base. However, exposing real world passwords to all developers with read access to the repository can lead to major security problems. It’s likely you don’t want to store sensitive data in your repository, thus you need to automatically generate the config.yml file somehow on deploy or on setup.

If you are using Capistrano to deploy your Rails application, you can ask Capistrano to generate and upload the file for you. Let me show you how.

One Problem, Many Solutions

As usual, one problem comes with many different solution. That’s good because this is definitely better than a problem without a reasonable solution… do you agree?

As Robert James pointed out in its email, there are at least 3 different approaches to solve this issue.

  • You can store sensitive data in your Capistrano deploy script. The downside of this solutions is that every developer with read access to the Capistrano script have access to the data as well.
  • You can store sensitive data on your server, but it requires some kind of manual setup.
  • You can use a password-less system, but this is probably the worst idea ever. Don’t misunderstand me, shared keys are a wonderful authentication system and I widely use them, but I didn’t find an effective alternative for database authentication.

My recipes combines the second choice with some additional features, taking advantage of Capistrano ability to execute commands simultaneously on multiple servers.

Capistrano database.yml task

The recipe is available as a gist. I think it is fairly self-explanatory and the documentation section at the beginning should give you a good overview of how it works.

#
# = Capistrano database.yml task
#
# Provides a couple of tasks for creating the database.yml
# configuration file dynamically when deploy:setup is run.
#
# Category::    Capistrano
# Package::     Database
# Author::      Simone Carletti
# Copyright::   2007-2009 The Authors
# License::     MIT License
# Link::        http://www.simonecarletti.com/
# Source::      http://gist.github.com/2769
#
#

unless Capistrano::Configuration.respond_to?(:instance)
  abort "This extension requires Capistrano 2"
end

Capistrano::Configuration.instance.load do

  namespace :db do

    desc <<-DESC
      Creates the database.yml configuration file in shared path.

      By default, this task uses a template unless a template 
      called database.yml.erb is found either is :template_dir 
      or /config/deploy folders. The default template matches 
      the template for config/database.yml file shipped with Rails.

      When this recipe is loaded, db:setup is automatically configured 
      to be invoked after deploy:setup. You can skip this task setting 
      the variable :skip_db_setup to true. This is especially useful 
      if you are using this recipe in combination with 
      capistrano-ext/multistaging to avoid multiple db:setup calls 
      when running deploy:setup for all stages one by one.
    DESC
    task :setup, :except => { :no_release => true } do

      default_template = <<-EOF
      base: &base
        adapter: sqlite3
        timeout: 5000
      development:
        database: #{shared_path}/db/development.sqlite3
        <<: *base
      test:
        database: #{shared_path}/db/test.sqlite3
        <<: *base
      production:
        database: #{shared_path}/db/production.sqlite3
        <<: *base
      EOF

      location = fetch(:template_dir, "config/deploy") + '/database.yml.erb'
      template = File.file?(location) ? File.read(location) : default_template

      config = ERB.new(template)

      run "mkdir -p #{shared_path}/db"
      run "mkdir -p #{shared_path}/config"
      put config.result(binding), "#{shared_path}/config/database.yml"
    end

    desc <<-DESC
      [internal] Updates the symlink for database.yml file to the just deployed release.
    DESC
    task :symlink, :except => { :no_release => true } do
      run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
    end

  end

  after "deploy:setup",           "db:setup"   unless fetch(:skip_db_setup, false)
  after "deploy:finalize_update", "db:symlink"

end

The following instructions basically represents the documentation you can find at the top of the original recipe.

Requirements

This extension requires the original config/database.yml to be excluded from version control. You can easily accomplish this by renaming the file (for example to database.example.yml) and configuring your SCM in order to ignore the database.yml file.

The following example demonstrate how to rename the file and ignore the original one with Subversion.

$ svn mv config/database.yml config/database.example.yml
$ svn propset svn:ignore 'database.yml' config

If your repository is powered by Git, type the following commands.

$ git mv config/database.yml config/database.example.yml
$ echo 'config/database.yml' >> .gitignore

If you don’t want to rename your file, there’s an other alternative. You can customize the recipe in order to force Capistrano to delete database.yml file after a successful deploy:code_update and before running the database:symlink task.

Usage

Include this file in your deploy.rb configuration file. Assuming you saved this recipe as capistrano_database.rb:

require "capistrano_database"

Now, when deploy:setup is called, this script will automatically create the database.yml file in the shared folder.
Each time you run a new deploy, this script will also create a symlink from your application config/database.yml pointing to the shared configuration file.

In case you need to run deploy:setup again and you don’t want Capistrano to ask for a database password, set the skip_db_setup option to true. This is especially useful in combination with capistrano multi-stage recipe when you already setup your server and you share the same environment across all the stages.

$ cap deploy:setup -s "skip_db_setup=true"

Custom template

By default, this script creates an exact copy of the default database.yml file shipped with a new Rails 2.x application.
If you want to overwrite the default template, simply create a custom Erb template called database.yml.erb and save it into config/deploy folder.

Although the name of the file can’t be changed, you can customize the directory where it is stored defining a variable called :template_dir.

# store your custom template at foo/bar/database.yml.erb
set :template_dir, "foo/bar"
#
# example of database template

base: &base
  adapter: sqlite3
  timeout: 5000
development:
  database: #{shared_path}/db/development.sqlite3
  <<: *base
test:
  database: #{shared_path}/db/test.sqlite3
  <<: *base
production:
  adapter: mysql
  database: #{application}_production
  username: #{user}
  password: #{Capistrano::CLI.ui.ask("Enter MySQL database password: ")}
  encoding: utf8
  timeout: 5000

Because this is an Erb template, you can place variables and Ruby scripts within the file.

For instance, the template above takes advantage of Capistrano CLI to ask for a MySQL database password instead of hard coding it into the template. This solves the original problem of storing sensitive data in your repository or deploy script.

Filed in Programming • Tags: , , , , ,

Comments

Mark says:

Hey, this was exactly what I was looking for!
Thank you very much for sharing this recipe.

Looking forward to more articles about Capistrano. :)

Blaenk says:

Thanks for the great script. For some reason though, the external file method did not work for me. The file was copied and all perfectly fine, but when I viewed it on my server, the variables were never expanded (It still showed #{var} and it didn’t ask me for the password). After copying/pasting the contents of the file into the HERE document directive, everything worked fine.

Thanks again I appreciate it.

kiran says:

This was an awesome post and was really useful in setting up my database.yml. Thank you so much.

Anuradha Mulukutla says:

This was extremely helpful, thanks! Just a small correction – for the substitutions (including the prompts for the password) to work correctly in the ERB file, I used ruby expression tags <%= … %> rather than the #{} string interpolation.

Pratik Khadloya says:

Very very helpful. Thanks a lot!

Josh says:

Hello! This looks really helpful, but where do I have to save the capistrano_database_yml.rb file? When requiring it in deploy.rb, I’m getting “`gem_original_require’: no such file to load — capistrano_database_yml.rb”…

You can save it where you want. For example, I usually store all deploy files in a /config/deploy folder.
Make sure the file is in your $LOAD_PATH.

Justin Sarma says:

Nice tutorial! You can enhance the security a bit by using password_prompt instead of ask. That way no one will see what you type on the screen:

Capistrano::CLI.password_prompt(“Enter MySQL database password: “)

Also, it’s a little off-topic, but the security hole that worries me much more than this is how database.yml has to store DB passwords on the server, so if the web server’s apache user account is compromised, so is the DB. You could encrypt the password in another file only readable by the Apache user, but the key to unlock it still has to be readable to the Apache user, so it’s ultimately security through obscurity. This may simply be an unsolvable problem. Gazzang’s ezNcrypt claims it can solve it with remote key servers, but that’s quite expensive. Any ideas, anyone?

Unfortunately it’s the way Rails works by default.

Andrew says:

I would like to run a script during deployment — and the script needs to access the database.

So I’d like to access database.yml during the deploy and be able to pull out the database access information so as to pass it to the script.

Is there a way to do this?

At the moment I use a .my.cnf file, so the script works with that information, but this is not optimal since I am storing the same information in two places. And it isn’t the rails way…

Steven says:

Andrew — If you’re still looking for a way to do access the contents of a YAML file, try this

task :get_dbinfo do
  info = YAML::load(File.open("config/database.yml"))["#{rails_env}"] if info.nil?
  {
    database: info['database'],
    password: info['password'],
    username: info['username'],
  }
end
Phil says:

skipping does not seem to work, at least not of this writing. Has this changed in capistrano? I can’t find a way to NOT have it do the setups!

Add a Comment




Follow Me
    Random Quote