Last 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.


Hey, this was exactly what I was looking for!
Thank you very much for sharing this recipe.
Looking forward to more articles about Capistrano. :)
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.
This was an awesome post and was really useful in setting up my database.yml. Thank you so much.
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.
Very very helpful. Thanks a lot!
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/deployfolder.Make sure the file is in your
$LOAD_PATH.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.
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…
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'], } endskipping 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!