Using Git’s post-receive hook to publish WordPress themes

We’ve been working on web projects as sole developers for years. But when we set up Proper Design, we realised that, as a group, we needed a better way to manage our publishing workflow. We’re big fans of Git (at least ⅔ of us are) and we’d read that this was possible with Git’s hooks, so we got cracking. This post is a mash-up of the different resources that we found that addressed bits of the problem.

This post assumes that:

  • You have your own virtual (or real) server with SSH access. If you don’t, you might want to try a Digital Ocean droplet. They’re cheap and only take seconds to spin up
  • You have Git up and running on your server and know some basic commands
  • You’re using a LAMP/LEMP stack
  • You do your development locally using a MAMP/XAMPP type server

These instructions are written for working on a WordPress theme, but can easily be adapted for other projects. We’ve documented the process for spinning up a test site on our shared server, but you can pretty easily adapt these instructions for your production server as well.

SSH into your server and create a new Git repo. On our server, we have a separate user for Git (called git, funnily enough), so we:

$ cd /home/git/
$ mkdir example.git ; cd example.git
$ git init --bare 

Back on your local machine, drop a clean WordPress install in your web root and install WordPress as normal.

We find that WP-CLI is a really nice way to install and manage WordPress from the command line. Hongkiat has a nice set of instructions of how to do this via Homebrew. Once this is setup, you can set up your WordPress install on your desktop.

We’re often building a new theme or plugin for clients, so we’ll carry on with the example of a WordPress theme.

# Make a directory for your new install
$mkdir /webroot/example/
$cd /webroot/example/

# Drop a copy of WordPress in it using WP-CLI
$ wp core download

# On your desktop, navigate to your new install's theme folder, e.g
cd /webroot/example/wp-content/themes/

Build your theme! Or in this case, we’re just going to drop a readme file in so that we have something to commit.

# Make our theme folder
$ mkdir example

# Create a readme
$ touch

Now we need to create the repo and add an origin. Thanks to Sonicq for this

$ git init
$ git add *
$ git commit -m "My initial commit message"
$ git remote add origin
$ git push -u origin master

If you’re another team member that’s cloning an existing repository, you’ll want to ignore the steps above and instead clone the existing repo into your themes folder:

$ git clone

Back on your server, it’s time to set up your new site. We use ISPConfig on our host to automate some of the setup. You might use Plesk, CPanel – or do it directly in the command line. ISPConfig creates a separate user for each new site, so we need to be careful about permissions.

Create your site (however you’d like), SSH into your server and change to your new site’s user in order to install WordPress.

$ su web44

We’ve found that with ISPConfig, our new user doesn’t have a bash profile, so instead we have to

$ su -s /bin/bash web44

Navigate to our new site’s web root on the server and install WordPress using WP-CLI.

# Our web root
$ cd /var/www/

# ISPConfig adds some default files to the new site which we don't need
$ rm -r *

# Install WP - we use WP CLI. Install it manually if you need to
$ wp core download

We’ve sometimes found that WP-CLI doesn’t clean up after itself and leaves files in /tmp/ that cause problems with subsequent installs. If this happens, simply delete the file in /tmp as mentioned in the WP-CLI error message.

A quick check to make sure that WordPress installed properly

$ls -l

-rw-r--r--  1 web44 client22   418 Sep 10 17:11 index.php
-rw-r--r--  1 web44 client22 19930 Sep 10 17:11 license.txt
-rw-r--r--  1 web44 client22  7192 Sep 10 17:11 readme.html
drwxr-xr-x  2 root  root      4096 Sep 10 16:49 stats
-rw-r--r--  1 web44 client22  4951 Sep 10 17:11 wp-activate.php
drwxr-xr-x  9 web44 client22  4096 Sep 10 17:11 wp-admin
-rw-r--r--  1 web44 client22   271 Sep 10 17:11 wp-blog-header.php
-rw-r--r--  1 web44 client22  4946 Sep 10 17:11 wp-comments-post.php
-rw-r--r--  1 web44 client22  2746 Sep 10 17:11 wp-config-sample.php
drwxr-xr-x  4 web44 client22  4096 Sep 10 17:11 wp-content
-rw-r--r--  1 web44 client22  2956 Sep 10 17:11 wp-cron.php
drwxr-xr-x 12 web44 client22  4096 Sep 10 17:11 wp-includes
-rw-r--r--  1 web44 client22  2380 Sep 10 17:11 wp-links-opml.php
-rw-r--r--  1 web44 client22  2714 Sep 10 17:11 wp-load.php
-rw-r--r--  1 web44 client22 33043 Sep 10 17:11 wp-login.php
-rw-r--r--  1 web44 client22  8252 Sep 10 17:11 wp-mail.php
-rw-r--r--  1 web44 client22 11115 Sep 10 17:11 wp-settings.php
-rw-r--r--  1 web44 client22 26256 Sep 10 17:11 wp-signup.php
-rw-r--r--  1 web44 client22  4026 Sep 10 17:11 wp-trackback.php
-rw-r--r--  1 web44 client22  3032 Sep 10 17:11 xmlrpc.php

Those permissions are fine for installing and using WordPress normally, however, we’re going to be using our git user to publish files directly to WordPress’s themes folder

# Give the group the same permissions as the user for the themes folder
$ chmod -R g=u wp-content/themes

# Check that our themes directory now has write permissions for the group
$ ls -l wp-content

# The result
-rw-r--r-- 1 web44 client22   28 Sep 10 17:11 index.php
drwxr-xr-x 3 web44 client22 4096 Sep 10 17:11 plugins
drwxrwxr-x 5 web44 client22 4096 Sep 10 17:11 themes

Now make your git user a member of your web user’s group

$ sudo usermod -a -G client22 git

Now we need to tell Git what to do after it’s received a commit. You’re probably going to need to do the next few steps as your git user.

$ su git

Edit or create the post-receive hook using your favourite text editor. We like nano because we’re not hardcore enough to get our heads around vim.

$ nano /home/git/example.git/hooks/post-receive

We’re going to add a bash shell script that gets executed whenever there’s a commit on the branch of our choosing. We’re forever indebted to geekforbrains for this one – it saved us hours.

For the time being, we’re only going to set up our build branch, but you can easily add a live/master branch by uncommenting the appropriate lines.

The shell script below uses rsync over SSH with the --delete option. The --delete option tells rsync to delete any files that aren’t present in the repo. This is good if you’re working on a theme folder where you want a 1:1 copy; however, be careful with this option if you’re syncing a whole CMS install with a comprehensive gitignore – it’ll delete your application if you’re not careful.


# your repo name here

# Dir paths on remote server
# These are associated with branches within a git project

if ! [ -t 0 ]; then
        read -a ref

# Get branch name from ref head
IFS='/' read -ra REF <<< "${ref[2]}"

# Make tmp dir for extracting files and cleaning up .git (we dont want them on the live site)
mkdir -p $tmpdir

# Assuming git is installed at /home/git/...
git --work-tree=$tmpdir --git-dir="/home/git/$REPO.git" checkout -f $branch

# If pushing to LIVE_BRANCH, deploy on LIVE
if [ "$LIVE_BRANCH" == "$branch" ]; then
  rsync -vzre ssh --delete "$tmpdir/" $LIVE

# If pushing to STAGE_BRANCH, deploy on STAGE
if [ "$STAGE_BRANCH" == "$branch" ]; then
  rsync -vzre ssh --delete "$tmpdir/" $STAGE

 rm -rf "/tmp/$REPO"

Make sure that our new file is executable

$ chmod +x post-receive

Back on our local development machine, we need to branch our example repo to create a build branch.

# Create a new branch called 'build'
$ git checkout -b build

# Push your new branch to your server. You'll probably need to make some changes and do a commit for this to work
$ git push -u origin build

And that’s it! Ok, so it’s a few steps, but it’s 15 minutes work to spin up a new client site, it’s well worth the investment for the productivity gains.

  • I tried to use the post-receive and it is giving me an error message. It says the last line rm -rf “/tmp/$REPO” unexpected end of file. Im confused where to put the tmp folder. Thanks! below is the error Im getting.

    remote: hooks/post-receive: line 69: unexpected EOF while looking for matching `”‘
    remote: hooks/post-receive: line 70: syntax error: unexpected end of file
    To ssh://

    • Andrew Shankletron

      Hi @cliftoncanady:disqus . I’m a bit confused too!

      Two things:

      1) The bash script only has 38 lines, so I’m a bit confused where lines 69 and 70 come from! Have you added some other lines? An unexpected end of file suggest an unclosed if/fi statement or possibly an unclosed string
      2) /tmp/ is a reference to a folder called /tmp/ in the root of your filesystem. If you’re running a unix-like system (Linux or Mac) this should already exist. Otherwise feel free to move this to a folder of your choosing – it’s literally a temporary place to store a snapshot of the contents of your repo.

      Hope that helps