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:
- Publishing happens the same way every time because it’s automated
- Publishing is simple – git push
- I can roll back breaking changes as easily as I push out new changes
- I can build more on this framework later if I decide to make it more awesome
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/phillipspiess.com cd git/phillipspiess.com git --bare init
You can verify that the git repo was created successfully with a quick:
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/phillipspiess.com cd jekyll/phillipspiess.com git init git remote add origin ../../git/phillipspiess.com
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/phillipspiess.com 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/phillipspiess.com/hooks 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.
#!/bin/sh echo "--------" echo "Pulling Branch Into Build Directory" echo "--------" cd /home/YOURUSER/jekyll/phillipspiess.com || 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/phillipspiess.com, 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/phillipspiess.com remote: * branch master -> FETCH_HEAD remote: eba7fb8..6d6ccac master -> origin/master remote: Updating eba7fb8..6d6ccac remote: Fast-forward remote: _drafts/deploying-this-blog.md | 22 ++++++++++++++++++++++ remote: _drafts/what-am-i-playing-wildstar.md | 9 +++++++++ remote: 2 files changed, 31 insertions(+) remote: create mode 100644 _drafts/deploying-this-blog.md remote: create mode 100644 _drafts/what-am-i-playing-wildstar.md remote: -------- remote: Running Jekyll remote: -------- remote: Configuration file: /home/YOURUSER/jekyll/phillipspiess.com/_config.yml remote: Source: /home/YOURUSER/jekyll/phillipspiess.com remote: Destination: /home/YOURUSER/www remote: Generating... remote: done. remote: -------- remote: Post Receive Hook Complete remote: -------- To ssh://YOURUSER@YOURSITE/~/git/phillipspiess.com master -> master
The method above is by no means:
- The cleanest way I could have done this
- The most advanced way I could have done this
- The coolest or most nerd-cred way I could have done this
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 firstname.lastname@example.org.