This technique isn't new. In fact, it's far from it. Around 2013, everyone seemed to be using this (or some variant of it) to deploy simple projects. But since then, it seems to have quiet down a bit. That's a shame, because it's super easy to use.
Here's how it works.
A git-push-to-deploy scenario
Imagine being able to do just the following to push a change to production.

This boils down to just 2 commands to push your change to production (or any release cycle, really):
$ git commit -am "Commit this awesome change" $ git push live master
This would be unthinkable in large projects (no code review, no approval process, ...), but for simple projects this is absolutely dead easy to use.
Preparations on the server
If you're reading "server" and thinking "wait a minute, I don't have a server", this probably isn't for you. You need a server (or at least: an SSH account, does not have to be root) in order for this to work. Shared hosting etc. probably won't work, unless you're given an SSH account which can execute git binaries.
Assuming you have the following directory structure for your site in your home directory:
$ ls -l htdocs logs
First, create a new directory that will contain your git repository data (the 'raw' data). I'll name it repo.git in this example.
$ mkdir ~/repo.git $ cd ~/repo.git $ git init --bare
Your server is now ready to 'host' a git repository: that's the magic behind git init --bare.
As a final touch, you need to add a post-receive hook to that repository. A post-receive hook is a simple bash-script (or any kind of executable, really) that gets called whenever this repository receives data from a git push.
We'll use this to trigger an update of your live site whenever a new change is committed to this repository.
$ vim ~/repo.git/hooks/post-receive
Copy/paste the following content in there.
#!/bin/sh git --work-tree=~/htdocs --git-dir=~/repo.git checkout -f git --work-tree=~/htdocs --git-dir=~/repo.git pull echo "Hooray, the new version is published!"
And make sure the file is executable.
$ chmod +x ~/repo.git/hooks/post-receive
When you first configure this, you'll need to make a first git clone manually to your htdocs directory (if this directory already exists, either move it temporarily (as a back-up) or remove it altogether).
$ git clone ~/repo.git ~/htdocs
Now you're all set to start receiving git pushes and auto-update your site.
Preparations on the client side
On the client-side, you only need to add a new git remote location. This will refer to your newly created repository.
From within the root of your git repository (the top most directory), add your new git remote.
$ git remote add live ssh://your_username@your_hostname.tld/~/repo.git
This will add a new repository to your git configuration named live which points to ssh://your_username@your_hostname.tld/~/repo.git. The endpoint may look cryptic, but it's just your SSH username @ server name or IP / path to your git repo.
If you were to list your git remotes locally, it would look like this (with different URLs, obviously).
$ git remote -v live ssh://username@ma.ttias.be/~/repo.git (fetch) live ssh://username@ma.ttias.be/~/repo.git (push) origin git@github.com:mattiasgeniar/Encoder.git (fetch) origin git@github.com:mattiasgeniar/Encoder.git (push)
For this example, I abused my string encoder/decoder tool, which you can also find on Github.
Now, whenever you do the following:
$ git commit -am "your commit message" $ git push live master
... your server will receive the data from the git push and it will trigger its post-receive you configured above, making a checkout in your htdocs folder from the git repository.
Boom, your change is live.
Update: as was pointed out to me by @pjaspers, there's a ruby gem called git-deploy that can help with most of the setup involved.
Comments
Ton Kersten Tuesday, December 29, 2015 at 23:31 -
Hello Mattias,
Nice article. Could you tell us what prompt you are using? Looks really good.
Mattias Geniar Wednesday, December 30, 2015 at 10:32 -
Hi Ton,
I’m using iTerm2 on my Mac combined with oh-my-zsh. I’m using the robbyrussell theme (here’s my full .zshrc config file).
Michelangelo van Dam Wednesday, December 30, 2015 at 00:40 -
Mattias,
A very nice description of how to automate your deployment workflow, I enjoyed the details you’ve given in your examples as well.
But these days sites are more then just code: they connect with databases, require image optimizations, need minifications of static assets, and so on. Therefore it’s wise to emphasize that these procedures are best used for stand-alone web applications and that these practices fit within a series of steps called a “pipeline”.
I have a presentation ready about this subject and with your permission I would love to give the talk at your office, enriched with the contents of this article (with credits).
Mattias Geniar Wednesday, December 30, 2015 at 10:38 -
Hi Mike! :-)
You’re absolutely right: this kind of ‘push to deploy’ works best for either static sites or very simple projects, where there isn’t much build involved anyhow.
However, you could take even this version further: nothing’s preventing you from running additional scripts on the
post-receivehook. It can trigger database migrations (and if there aren’t any, it could just skip this step), minify your CSS, restart worker daemons, …It has its limitations, so like you mentioned it’s safe to look at a tool which offers you more variety and configuration options if your build process is complex.
As for your presentation: I would love to see it! Perhaps we can combine it with a PHP Benelux meetup?
Šime Vidas Wednesday, December 30, 2015 at 02:02 -
What if the site’s JS/CSS assets are generated via (gulp) build tasks? Would you check in the generated files (in addition to their sources)?
Mattias Geniar Wednesday, December 30, 2015 at 10:34 -
I think this is personal preference, but I don’t mind committing the generated files in my repo. Some like it, some swear against it.
Alternativally, you could just have the
post-receivescript run all gulp/grunt/composer/… buildsteps you need, so every time you push to your remote repository those steps are executed automatically (on the server, not on the client).However, this easily becomes a scenario where you’re hitting
git‘s limitations and you’re better of using an actual build tool like Capistrano, Phing, Make, Deployer, …Francis Kim Wednesday, December 30, 2015 at 15:19 -
I’ve been looking for a similar solution for a while, I’ll definitely be trying this out at work. Thanks!
Robin Thursday, December 31, 2015 at 00:37 -
Nice write up. We use basically the same method of deploying our dockerized containers to a remote server which receives the code, rebuilds (if need be) the container remotely, runs the composer/npm install et al if need be and restarts the app. And by utilizing the docker infrastructure this can be done without (noticable) downtime :)
If anyone is interested: have a look at the dokku project (https://github.com/dokku/dokku) which does all this using (fairly) straight forward bash scripting and git hooks ;)
(but boils down to what you described)
Cool!
David Bain Saturday, June 18, 2016 at 14:29 -
Just a note: I had to swap out ‘~’ with ‘$HOME’ and then the script worked for me.
“`
#!/bin/sh
git –work-tree=$HOME/htdocs –git-dir=~$HOME/repo.git checkout -f
echo “Hooray, the new version is published!”
“`
Mike Thursday, May 11, 2017 at 03:07 -
I don’t understand why the post-receive hook is doing a git checkout instead of a git pull? A checkout doesn’t get updates where as a pull does. What am I missing? Thanks, Mike.
Mattias Geniar Thursday, May 18, 2017 at 13:19 -
Seems something went wrong in the post editing, should now be fixed! The checkout resets the current git structure, so the “git pull” doesn’t give any merge conflicts.
Post should be updated correctly now. Thx for the heads-up!
Tom Hag Thursday, November 30, 2017 at 20:23 -
Hi this is rather old, but I was trying to do this for a simple git deployment setup, and have followed you guide. Everything seems to work, but somehow the post-receive hook will not fire after pushing a commit to the repo on the server. So the commit is confirmed pushed to the server repo, and so far so good, however the git hook will not fire and I have to manually pull the file from the repo to the www directory. File execution permission is also ok.