Anton Zolotov

I'm a software developer, entrepreneur and CEO of GunpowderLabs, a web and mobile development consultancy. Contact me.

Rails Scripts: Clone Heroku Database to Development

Sometimes, you need to quickly clone your production database to your local environment. Taps is an option, but at 10 minutes, it’s way too slow for some of my databases.

The faster option is to create a Postgres dump for your database, download it, and import it locally. Initially, I used this code, which lived in each project’s readme, and copied it into the terminal.

heroku pgbackups:capture --expire --remote production
curl -o latest.dump `heroku pgbackups:url --remote production`
pg_restore --verbose --clean --no-acl --no-owner -h localhost \
    -U azolotov -d project_development latest.dump
rm latest.dump

That’s better, but not good enough, because the snippet has to be modified for each project, and user who uses it.

The better solution

I wanted a short script that is run with one command. It should have sensible defaults that can be overriden and gather all information from it’s environment.

Here’s what I came up with:

#!/usr/bin/env ruby

module Heroku
  class Db < Thor
    method_option :keep,   :type => :boolean, :default => false
    method_option :remote, :type => :string,  :default => "production"
    method_option :host,   :type => :string,  :default => "localhost"
    method_option :user,   :type => :string,  :default => `whoami`
    method_option :dbname, :type => :string
    method_option :dump,   :type => :string,  :default => "latest.dump"

    desc "clone", "clone a remote heroku database to the local environment"
    def clone
      puts "Cloning production database to local environment. This might take a few minutes\n"
      puts "(1/4) capturing production database snapshot..."
      puts `heroku pgbackups:capture --expire --remote #{options[:remote]}`
      puts "(2/4) downloading snapshot..."
      puts `curl -o #{options[:dump]} \`heroku pgbackups:url --remote #{options[:remote]}\``
      puts "(3/4) restoring snapshot..."
      puts `pg_restore --verbose --clean --no-acl --no-owner -h #{options[:host]} -U #{options[:user]} -d #{options[:dbname] || dbname} #{options[:dump]}`
      unless options[:keep]
        puts "(4/4) cleaning up..."
        puts `rm #{options[:dump]}`
      else
        puts "(4/4) skipping cleaning..."
      end
    end

    no_tasks do
      def dbname
        YAML.load_file('config/database.yml')["development"]["database"]
      end
    end
  end
end

Thor

I use thor because it’s very lightweight and makes passsing options to the script a breeze. From thor’s description:

Thor is a simple and efficient tool for building self-documenting command line utilities. It removes the pain of parsing command line options, writing “USAGE:” banners, and can also be used as an alternative to the Rake build tool. The syntax is Rake-like, so it should be familiar to most Rake users.

Usage

Make sure you have thor installed

gem install thor

Put the script in your Rails root directory, and run it with

thor heroku:db:clone

Assumptions

  • The production application lives under the git remote production.
  • The database host is localhost.
  • The database user is the same as the currently logged in user.
  • Import into the development database as specified in config/database.yml.
  • We don’t need the dump file after importing it.

Configuration

If those assumptions don’t match your environment, you can override them:

To keep the dump file after importing it:

thor heroku:db:clone --keep

To change the name of the remote:

thor heroku:db:clone --remote staging

To change the name of the user:

thor heroku:db:clone --user bob

blog comments powered by Disqus