Simplest Continuous Deployment with git push

The past two years working for Amazon have taught me a ton about the importance of moving fast with simple tools. (Quick reminder, everything on this blog is my own, and does not represent the opinions of my employer.) One of the core reasons I decided to move off Wordpress was its complexity. It was a powerful tool, but I wasn’t always sure what it was doing on my behalf. I’m just displaying some formatted text, I don’t need a database, and admin control panel, user accounts. I don’t need to worry about keeping a whole suite of software up to date. Now I run simply with tools I’m very familiar with – Markdown, Ruby, nginx, and git.

Motivation – Stop at git push

Like many Amazon devs, I’m used to deploying to production multiple times a day. When I git push in the office tiny wizards carry my code to production in a process that is generally known as Continuous Delivery. At home, the process for deploying my new Jekyll blog was nowhere near as fluid. I found myself ssh'ing into a server, running Jekyll by hand, moving some files around, generally doing all the stuff after git push that is much less interesting than writing code (or blog posts.) What I wanted was a process, no matter how simple, that let me stop at git push.

What I built isn’t exactly complicated or ready for use in a real production situation, but it gives me a couple of key benefits:

My Setup

I’m running on a basically vanilla Ubuntu VPS. I’m going to assume you’ve got some sort of workflow you’d like to kick off as well. I started with a folder on my desktop I was scp'ing up to my VPS, then building and copying by hand. I’m going to walkthrough how straightforward it was to set up both machines with an existing set of files.

Preparing the Server

You could very easily use Github as your bare server repository, and perhaps at some point in the future I’ll do just that, but simplicity is king. I’d have to stand up something new to respond to the POST messages Github sends out. If we set up the bare repo on the VPS itself, we can write whatever hooks we want.

So, first things first, install git:

sudo apt-get install git

We need a place to act as the bare repository, the central repo that doesn’t actually contain any of our files explicitly on the disk. I stuffed mine in a directory in my home dir.

cd ~
mkdir -p git/
cd git/
git --bare init

You can verify that the git repo was created successfully with a quick:

git status

Create the Serverside Jekyll Directory

We’ve got a bare repo set up, but we need somewhere that we can use to actually stage all the files after a commit. Basically, you are going to need to run Jekyll against something, so we’ll set up another git repo on the box to pull from the bare one. I set up a jekyll folder in my home directory for this.

We create a standard git directory, and set up the origin remote.

cd ~
mkdir -p jekyll/
cd jekyll/
git init
git remote add origin ../../git/

Next up, let’s set up git on the desktop machine we’ll be using to author posts.

Git On Your Desktop

You’ll need to install git on the machine you are authoring from. Maybe you’ve already got it installed like I did. If not, there are a million tutorials out there on setting it up. What we’re interested in is taking an existing pile of Jekyll files and turning them into a functional git repo. I use git bash for this, because I’m on my Windows box.

cd to/my/directory/with/my/precious/files
git init
git remote add origin ssh://MYUSER@MYHOST/~/git/
git add .
git commit -m "Initial commit"

With that, I now have three repos. One to work out of, one to build out of, and one to serve as a central repo. But commiting to origin/master still doesn’t actually deploy your site. We’ve just laid the groundwork. Now it’s time to work the magic.

Wizard Magic (Or Git Hooks)

Ok, this part isn’t really magic, it’s actually a very well documented and understood feature of git. The basic idea is to trigger all the steps I would take manually every time I push. Git will helpfully run scripts in the ‘hooks’ directory on specific events. In this case, the event we’re interested in is post-receive – executed after we send code upstream.

To create the hook, we just need to ssh back to our server, and create a new file. There’s a key step here that I forgot, and tripped me up for a bit – the file you create needs to be executable! Don’t forget to chmod +x it, or git will helpfully and silently ignore it.

cd ~/git/
touch post-receive
chmod +x post-receive

Now you have an executable file that will run whenever you commit to your directory, we just need to put in the instructions we want executed, using vim (or your favorite text editor) add the following to the post-receive file.


echo "--------"
echo "Pulling Branch Into Build Directory"
echo "--------"

cd /home/YOURUSER/jekyll/ || exit
unset GIT_DIR
git pull origin master

echo "--------"
echo "Running Jekyll"
echo "--------"

jekyll build -d /home/YOURUSER/www

echo "--------"
echo "Post Receive Hook Complete"
echo "--------"

I went a little wild with the echo statements. Sue me.

This simple post receive hook does very few things. First it navigates to the build directory in ~/jekyll/, which is the second repo we set up. Then it unsets the GIT_DIR environment variable, which forces git to look locally for a .git folder. Then it downloads the latest master branch to our staging area.

Then we build our site! I build mine to a directory shared with a Docker image running nginx, but you can send it anywhere. (Also, in this case, I know you could also include the -d parameter in my _config.yml, but I wanted to show my intent during a build.)

Watch the Gears Turn

This is always my favorite part. If you’ve got everything set up correctly, you should be able to git push from your desktop and get a charming little report as your site rebuilds.

Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 1.46 KiB, done.
Total 5 (delta 1), reused 0 (delta 0)
remote: --------
remote: Pulling Branch Into Build Directory
remote: --------
remote: From ../../git/
remote:  * branch            master     -> FETCH_HEAD
remote:    eba7fb8..6d6ccac  master     -> origin/master
remote: Updating eba7fb8..6d6ccac
remote: Fast-forward
remote:  _drafts/        | 22 ++++++++++++++++++++++
remote:  _drafts/ |  9 +++++++++
remote:  2 files changed, 31 insertions(+)
remote:  create mode 100644 _drafts/
remote:  create mode 100644 _drafts/
remote: --------
remote: Running Jekyll
remote: --------
remote: Configuration file: /home/YOURUSER/jekyll/
remote:             Source: /home/YOURUSER/jekyll/
remote:        Destination: /home/YOURUSER/www
remote:       Generating...
remote:                     done.
remote: --------
remote: Post Receive Hook Complete
remote: --------
    master -> master


The method above is by no means:

But that doesn’t matter. I will argue into the earth that your tools should be there to support you in doing what you need to be most productive. If that means grabbing something off of Github, installing it, and forgetting about it, perfect! For me, it’s about having something I can quickly tear down and tinker with. Something I understand more or less completely, and something I can always improve on later.

As always, if you find any errors, or have any feedback, reach me at