3/2/2016 - 3:31 AM



from Kris Jordan

November 2nd, 2013 Setting up Push-to-Deploy with git

I first set up a push-to-deploy system with git and puppet for a side project a few years back. It worked so well I transitioned NMC's development process onto it for all of our new projects starting last year. It offers the simplicity of the "push-to-deploy" model Heroku pioneered, with full control and flexibility over the operating system environment.

I've started thinking about my next iteration of this system for Didsum, and using pushing for more than just deployment purposes. The Push-to-______ pattern is powerful and easy to use, once you know how the pieces fit together. In this post, I'll walk through the setting up Push-to-Deploy from the ground up.

(I'm assuming a working knowledge of: terminal, git, and a scripting language.)

Preparing our Repositories

Let's keep things straightforward by placing our development git repository, remote git repository, and deploy directory under the same local directory.

mkdir push-to-deploy cd push-to-deploy mkdir {development,remote,deploy} Awesome, now let's setup the remote git repository. With a real project, this would be a directory on your production server. There is a special setup for git repositories whose purpose is to receive pushes from developers, they're called "bare" repositories. You can read more about "bare" repositories, but for our purposes their purpose is just to receive pushes.

cd remote git init --bare cd .. Perfect, now let's setup our development directory with git. (You could also copy one of your git project's contents into the development folder.)

cd development git init echo "Hello, world." >> file.txt git add file.txt git commit -m 'First commit.' We now have a development repository with its first commit. Our last preparation step is to register the "remote" repository. If you're fuzzy on git remotes, the official git site has you covered.

git remote add production ../remote git push production master Boom. You've just pushed your commit from development to your bare, 'remote' repository. We're ready to setup push-to-deploy.

Set up Push-to-Deploy

Now that our remote repository is setup, we're ready to write a script for what it'll do when it receives a push. Let's navigate to the hooks folder of our remote repository. Hooks are scripts that git runs when certain events happen.

cd ../remote/hooks touch post-receive chmod +x post-receive The hook we care about for push-to-deploy is post-receive. It is run after receiving and accepting a push of commits. In the commands above, we're creating the post-receive script file with touch, and making sure it's an executable file.

Next, open the post-receive script file in your preferred text editor. Copy the contents below:

#!/usr/bin/env ruby


1. Read STDIN (Format: "from_commit to_commit branch_name")

from, to, branch = " "

2. Only deploy if master branch was pushed

if (branch =~ /master$/) == nil puts "Received branch #{branch}, not deploying." exit end

3. Copy files to deploy directory

deploy_to_dir = File.expand_path('../deploy') GIT_WORK_TREE="#{deploy_to_dir}" git checkout -f master puts "DEPLOY: master(#{to}) copied to '#{deploy_to_dir}'"

4.TODO: Deployment Tasks

i.e.: Run Puppet Apply, Restart Daemons, etc

Let's walk through each of the steps:

  1. When git runs post-receive, the data about what was received is provided to the script via STDIN. It contains three arguments separated by spaces: the previous HEAD commit ID, the new HEAD commit ID, and the name of the branch being pushed to. We're reading these values and assigning them to from, to, and branch variables, respectively.

  2. Our purpose here is to automate push-to-deploy. Assuming a workflow that keeps production on the master branch, we want to exit this script prior to deploying if the branch being pushed is not master.

  3. The first deploy step is to "checkout", basically export or copy, files from the master branch to the directory where our project is deployed to in production. (Remember, in this demo it's the fake "deploy" directory, in the real world this might be /var/www, or wherever your project expects to be in production.)

  4. Now that our deploy directory is up-to-date, we can run whatever deployment tasks we need to run. This could be applying Puppet scripts (I'll write a post on this scenario soon), restarting a web or application server, clearing cache files, recompiling static assets, etc. Whatever steps you'd normally need to do manually after updating your project's files, automate them here!

Save your post-receive hook, and let's test it out!

Testing with Pushing

We can test our script manually, by creating a new commit in our development directory and pushing:

cd ../../development echo "New line." >> file.txt git add file.txt git commit -m 'Testing push-to-deploy' git push production master In the output of the git push command, you should see lines starting with "remote:". These lines are the output of our post-receive script:

Already on 'master' DEPLOY: master($TO_ID) copied to 'push-to-deploy/deploy' The first line is noisy output from the git checkout command in step 3, we can ignore it. The second line, is from the puts command, also from step 3 in our post-receive script.

The directory we're deploying to should now be populated and up-to-date:

ls ../deploy diff file.txt ../deploy/file.txt Pretty awesome, right?

Testing without Pushing

When you're working on a post-receive hook, it's annoying to muck up your project's commit history and push each time you make a change. Luckily, because it's just a script, we can fake it from the command-line.

cd ../remote git log -2 --format=oneline --reverse First, we need to get the IDs of our most recent 2 commits. The git log command, above, will give us these two IDs in the order you'll want to replace the $FROM_ID and $TO_ID variables with, respectively.

echo "$FROM_ID $TO_ID master" | ./hooks/post-receive This method makes setting up your post-receive hooks enjoyable, enabling you to quickly iterate on your script and execute it repeatedly.

Next Steps

In this post, we've walked through how to setup the push-to-deploy with git. For a real world project, your 'remote' and 'deploy' folders would usually be setup on a server, not locally. The details of doing that and properly configuring SSH is beyond the post of this scope (note to self: I should write on SSH configuration, too!).

From here, it's up to your project to determine what actions to automate! Happy pushing!

(If the push-to-X pattern interests you, you should follow me on Twitter, or subscribe to my RSS feed, because I've got a few more posts on the subject coming up! Also, feel free to tweet feedback / questions / topics you'd like to read.)

Share this post

Twitter Facebook Google+ Older Posts Copyright © 2016 Kris Jordan - All Rights Reserved - Powered by the HiFi Hosted CMS