Tutorial / December 17, 2008

Multi-staged deployment with versioning using git.

By John Eberly/5163 Views/6 Comments

When moving to git, one of the first questions that came up, was "How do we deploy using git?". The easy route would be to follow the same pattern we used under subversion and keep a separate branch for each environment, i.e Production and Staging branches. But we wanted more. We wanted SCM (software configuration management) and deployment procedures that take advantage of git's "cheap" branching and support versioning for our apps.

The following diagram approximates what our current version procedures look like using git and capistrano. 130afd3a2cf429b65e6e696066eaf2c566191b4e_1229556997_0 Credit: Perforce.com

Read on to see how we currently do it...

1. What we wanted.

  • Deploy to multiple stages, eg. build, staging, production
  • Have versioning for our app and have it represented in our SCM, Version 0.4, release 0.4.1
  • Be able to deploy any version to any stage (with restrictions on production)
  • Take full advantage of cheap git branching and not recreate the deployment procedure used with subversion (eg. staging/production branches)

We also wanted to follow SCM best practices. Perforce provides a very good overview.

2. How we implemented it using git and capistrano

In our case:

  • Version == Git remote branch
  • Release == Git remote tag

This is our how our process works on a high level.
  1. Create Version for each sprint
  2. Deploy Version after each Demo
  3. Create bug fixes on Version Branch
  4. Deploy Version branch and tag it with a release number (repeat as necessary)
  5. Merge down bug fixes to master

3. Enough theory, lets actually make it work.

The following assumes you already have a working capistrano setup with multi-staged deployment. Add the following to your deploy.rb to enable prompting for a branch on cap deployment. Also, remove the ':set branch' from your deploy.rb file.

  namespace :deploy do
    task :default do
      set(:branch) do
        br = Capistrano::CLI.ui.ask "What branch do you want to deploy?: ".downcase
        raise "Cannot deploy master branch to production." if (stage.upcase == 'PRODUCTION') && br == 'master'
        br
      end
      puts "*** Deploying to the #{stage.upcase} server!"
      update
      restart

      # cleanup old deployments
      deploy.cleanup

      # send deployment notification, except for the default
    end

    # override migrations task to inject branch
    desc <<-DESC
      Deploy and run pending migrations. This will work similarly to the \
      `deploy' task, but will also run any pending migrations (via the \
      `deploy:migrate' task) prior to updating the symlink. Note that the \
      update in this case it is not atomic, and transactions are not used, \
      because migrations are not guaranteed to be reversible.
DESC
    task :migrations do
      set :migrate_target, :latest
      set(:branch) do
        br = Capistrano::CLI.ui.ask "What branch do you want to deploy?: ".downcase
        raise "Cannot deploy master branch to production." if (stage.upcase == 'PRODUCTION') && br == 'master'
        br
      end
      puts "*** Deploying to the #{stage.upcase} server!"
      update_code
      migrate
      symlink
      restart

      # cleanup old deployments
      deploy.cleanup

      # send deployment notification, except for the default stage
    end
  end

Unfortunately, creating remote branches and tags in git is ambiguous. For each new version, create a remote branch that represents a version.

  git push origin origin:refs/heads/version-0.1

Git does not allow you to work in remote branches, so create a local git branch that tracks your remote branch (version).

  git branch --track v0.1 origin/version-0.1

Checkout your version branch, fix bug, and do a git push. Prior to each deployment, create a git remote tag incremented for the next release.

  git tag release-0.1.1
  git push --tags

Deploy

  cap deploy
  ....
  What branch do you want to deploy?: version-0.1

Repeat tagging procedure for each bug until next version.

After your are finished with a particular Version, merge changes back to master.

  git checkout master
  git merge version-0.1

All of this requires slightly more overhead than you may be used to, but it gives you proper versioning for your app and it is all handled by git. Also, we have created rake tasks to handle the creation of remote branches and tags, but I have omitted then to keep this post from getting too long. Leave a comment if you would like to know more.

4. This is still a work in progress

Although this is working well for us, it is not a perfected process yet. Also, remember don't delete tags/branches that have pushed to other git repositories. Finally, git cherry-pick can be very handy for merging commits from one branch to another without needing to do a full merge.

Comments

Posted by Doug Ramsay on about 1 year ago3995746311ee0b2b235a51c341807ce1?s=30

Thanks for the write-up - very helpful.

I'd be interested in hearing more about the rake tasks for the remote branches and tags.

Posted by Jason Scragz on about 1 year ago6b2bc83a3bc658e14e869307c6b88356?s=30

I'd like to see the rake tasks as well.

Posted by zoe somebody on about 1 year ago6c920fb77068f4da2479f4b483fa263f?s=30

Thanks for the article. I too would benefit from seeing the rake tasks for remote branches and tags.

Posted by Jason Scragz on about 1 year ago6b2bc83a3bc658e14e869307c6b88356?s=30
Posted by John Eberly on about 1 year agoAf64e71caa3ab3aeb8abff3d8f47c875?s=30

Thanks everyone for your comments. I hope to post more information on our rake tasks soon.
@Jason, grb is definitely one way to go; it helps make remote branching more obvious in git. Our rake tasks, do a little bit more than just create a remote branch or tag. They also suggest a release name based on the previous release tag name.

Posted by Robert on 7 months agoE2e576c5ae527f31e5c21bb9d265bbc7?s=30

Another practice I've seen with larger environments is to have a shell account with a checkout that tracks the stable branch specifically set up to act as the capistrano control point. You ssh in and run cap commands there instead of locally. This can help avoid issues where your local checkout's deploy.rb has modifications that you aren't ready to use with deploying to production. This is less of an issue with git vs svn, but still one must be careful to think about what their local deploy.rb is at the moment they're running cap commands.
http://www.bingo-bonusse.de/

Add a Comment