Simple Deployment with Git

The gist of this is that we’re taking a local folder of a project, pushing it to a bare repo on the server, then the server runs a post-receive hook to check out the latest commit to a live folder on a server.

What follows are my notes for how to make this happen using the example of how my blog is deployed:

To start with you should create the bare git repo on the server. I’d suggest keeping this out of the folder you deliver your sites from for security reasons. You do you though. It just needs to be accessible as a remote. Mine is in /var/repos/blog:

git init --bare

Once it’s created you need to navigate to the /hooks folder of the repo. Then you’ll add a file for post-receive. This runs after the commit is completed. You can read all about git hooks here. They’re quite useful.

The post-receive hook

This is a potentially destructive act in that it resets the working directory to your latest commit. If you ever modify a file on the server the next commit will blow it away. That’s okay though because you’d never modify a file on the server outside of version control, right?

Also note that if you create a file inside of the folder on the server it will still exist later on. Git isn’t going to delete it unless it’s removed as part of the commit.

Don’t muck around in that folder is what I’m saying.

Here’s that post-receive script:

#!/bin/sh
git --work-tree=/var/www/chrissalzman.com/blog/ --git-dir=/var/repos/blog/ checkout -f
echo “Your blog has been updated at chrissalzman.com/blog/”

The meat of this is running a git checkout command that explicitly uses a “work tree” and a “git dir”. Generally, you don’t need to set these for a checkout if you’re in the folder that is associated with the repo you’re working with. This is amazing flexibility, albeit sort of confusing until you need it. Stackoverflow is littered with questions about why these flags even exist. It’s also important to note that you should set up the “work-tree” before you leave the server. This script will not create it for you. Making the script do so is left as an exercise for the reader.

The -f (—force) flag does the following:

When switching branches, proceed even if the index or the working tree differs from HEAD. This is used to throw away local changes. When checking out paths from the index, do not fail upon unmerged entries; instead, unmerged entries are ignored.

The echo’d line gets sent back to the terminal you’re pushing from. I’m using it to remind myself of what this all does under the assumption that I’ll forget what I did later on. Remember: your future self thinks you are dumb. Explain your work.

You’ll likely also need to modify the permissions on the file to run. For me this was sufficient:

chmod 775 post-receive

Then back on your local machine initialize your git repo in the folder you want to use:

git init 

And we’ll add in a remote located at the bare repo. For me it was this:

git remote add live ssh://user@domain.com/var/repos/blog/

This use case is making the project “live” so I’m calling the remote “live”. You may call it what you’d like. Go ahead and commit locally and then push it to live:

git push live master

If all goes well what should happen is that you push master to “live”, then the post-receive hook runs and it checks it out to the live directory. And you’re deployed!

Last step is running to the server to see that, yes, the blog post really did show up.