Managing WordPress Theme Deployments with Git

You’re using version control for all of your code, right? One part of the process that took me longer than I would have liked to master is deployments via git. I use Bitbucket for all of my private repositories (I have a free unlimited account, so I haven’t found a need to pay for Github) and I wanted to find a way to deploy via the command line in the same way I push code to Bitbucket. After a lot of searching and trials and errors, I’ve come up with a good system that works for me. It might not be the best or most efficient, but it’s what makes the most sense to me. Most of my deployments will be WordPress themes and plugins that I develop for clients, so that’s where I focused my efforts.

I read a lot of code examples and blog posts while searching for this technique and a few things never clicked for me, so I wanted to highlight the process for anyone that might have been in the same boat as me.

Before we start, let’s get some requirements out of the way:

  • You have SSH access to your server (if you have shared hosting, make sure you can get SSH access)
  • Git is installed on your local machine and on your server
  • You have a local git repository already setup with your code (I’ll be using a WordPress theme as an example)

I’m using a Mac for local development, so if you’re using Windows, things will be slightly different in how you access the command line.

What’s this Capistrano thing?

Capistrano is a terrific deployment tool build on Ruby. You can use it to automate deployments, quickly roll back to previous versions, etc. I spent a good part of a day learning Capistrano and what it can do, only to find out it won’t work with shared hosting environments very well. Why is that?

Well, Capistrano creates a directory structure in your deployment location (using /public_html as an example):

-- releases
-- shared
-- current (symlink pointing to your latest deployed code in /releases)

The problem here is that to effectively use Capistrano, you need to change your Apache settings to point the DocumentRoot to /public_html/current, which isn’t possible with most shared hosts (or most hosts running cPanel). So, while it’s a great tool, it’s not the tool for what I need.

Git Remotes to the Rescue

You should be aware of git remotes by using Github, Bitbucket or some other repository management system. It’s a way to push your code changes somewhere offsite for sharing, collaboration, backup, etc. With any repository, you can add as many remotes as you’d like. So let’s get right to it.

In this example, I’m going to use a WordPress theme as my deployment code. I’m not using the entire WordPress structure as the repository for a few reasons (simplicity in the case of this example, and also for needs of the client website that I’m developing).

Here’s a look at the local repository:

To create your repository, open Terminal (or iTerm 2, which I strongly prefer) and navigate to your code directory:

cd your-project
git init
git add .
git commit -m "initial commit"

We’re now ready to create a remote repository. I’m using a shared hosting environment with Bluehost for this example deployment, where I have an SSH connection setup with public key authentication. The big thing to remember here, which took me a while to grasp, is that the remote repository you’re going to push to will be a separate directory than the actual WordPress theme we’re deploying. Locally, the repository is always the code directory, but for remote deployments, we need to do things a bit differently.

So, let’s connect to our server (replace your user details and directory structure). We’re going to a create a folder named repos outside of the public_html directory that will house our remote repositories.

mkdir repos && cd repos
mkdir your-project.git && cd your-project.git
git --bare init

We’ve now created a bare repository on our web server that we will use as our remote location. Still with me? We’re getting closer! The next step is to setup our post-receive hook. This is a script that will run every time you push code to the remote repository. Here’s how to set that up (I’m going to use vim, but you can use any editor you choose – you’ll run this while still connected to your server by SSH):

vim hooks/post-receive

Now, put this code into your post-receive file (replacing the path with the full path to where you want the code deployed).

GIT_WORK_TREE=/home/myuser/public_html/wp-content/themes/mytheme git checkout -f

After that’s done, you’ll need to give the post-receive file execute rights so it’ll actually do the work:

chmod +x hooks/post-receive

[UPDATE: Thanks to Martin Sjåstad for pointing out I forgot the chmod +x line!]

That’s it! We’re now ready to deploy our code by just using:

git push

Let’s Deploy that WordPress Theme

Back on our local machine, let’s add our server to the git remote (replace myserver with whatever name you’d like to give the remote and add your login credentials):

cd your-project
git remote add myserver ssh://
git push myserver +master:refs/heads/master

Your code will now be pushed to the bare repository you created, and then the post-receive hook will checkout the code to the location you specified. In my case, that’s my theme directory:


To update your code later with a new push, just run:

git push myserver

That wasn’t so bad, was it?

While it might be daunting for those new to version control, it’s really a quite simple process once you wrap your mind around keeping your bare repository separate from your deployed code. I’ve used services like Beanstalk in the past for deployments, but decided I didn’t want to pay for that when I can do it myself easily from the command line.

Hopefully this helped some of you that were in the same position I was while setting up remote deployments. A lot of credit is due to an article I found from Abhijit Menon-Sen. Also thanks to Mubashar Iqbal, Jack McDade and Jonathan Christopher for tips. If you have thoughts on how to do this better, by all means let me know in the comments or send me a message on Twitter (@mattbanks).


  1. David Smith says

    Hi Matt,

    Great article. Clear clean and concise.

    One question – could you explain the line:

    git push myserver +master:refs/heads/master



  2. David Smith says

    Just to clarify one point in your tutorial. For this:

    GIT_WORK_TREE=/home/myuser/public_html/wp-content/themes/mytheme git checkout -f

    The line

    git checkout -f

    …means it will assume a master branch.

    I only wanted to checkout a develop branch and never the master and so I altered this to:

    git checkout develop -f

    A more automated solution for managing branchs is detailed at:

    …but it’s overkill for me.

    • says

      Ahh yeah, I wrote this up with master branches in mind since I do all development locally and push the master to my preview and production servers, but you can easily just change the branch to checkout like you said.

  3. says

    Loved the article! I was running git-ftp to push changes, but bluehost didn’t like the time it took or the ftp calls it was doing so every time I tried to run git ftp push bluehost would lock my account out for “suspicious ftp activity”. So this was godsend.

    This might seem like a nobrainer, but in the step where you edit the post-receive hook, you have chmod +x it for it to properly move the files after pushing.

  4. Nicholas says

    Great article. I have just 1 question regarding plugins, as I am in the process of reviewing automated deployment of these.

    It would appear that a plugin must deactivate prior to the files being upgraded, and then reactivated. As such, overwriting an active plugin could present difficulties, as the deactivation hooks may not be run on the old version.

    Is this your understanding also, or have I mistaken the process in my reverse-engineering of it.

    • says

      You should be able to just drop in the plugins without deactivating first. I don’t think most plugins require deactivation before updating, so you should be good. Since you’re working locally anyway, you can test it out by updating the plugins without deactivating them, then pushing everything up to the remote repository.

      I’m only pushing the theme files here, although my workflow for pushing entire WordPress sites is basically the same.

  5. says

    This is a very informative tutorial I have chosen to use the command line git for home version control and sharpen unix up instead of relying on tools like tower and beanstalk. Those tools make sense for busy work things but I dont want my brain to get lazy.

  6. Sebastian says

    Thanks for your tutorial. It looks great. Unfortunately I haven’t been able to complete it so far. When I try to push changes to my remote server, I get an error: path/repos does not appear to be a git respository.

    Any ideas?

    • says


      When you add the git remote, are you adding the location of the bare repo, or the actual location of the theme files? It should be the bare repo location. Sounds like you might have set it to the location of theme files in wp-content, or the path you have to the bare repo might be wrong.

      git remote add myserver ssh://

      • Sebastian says

        Hey Matts,
        thanks for your reply!

        I finally figured it out. I pointed my remote simply to repos as I don’t have a git file in it. The other problem was that my actual web folder with wordpress isn’t located in the home folder of my ssh user. I didn’t adapted the path of the git work tree accordingly in the post-receive hook.

        I really hate that git stuff. Hopefully it was worth it. :-)
        Thanks again for your great tutorial. Best resource I found!

  7. says

    Hey Matt!

    Thank you much for this article, this really saved me some hair pulling and a few bucks. I’ve been wanting to incorporate some sort of deployment mechanism with my version control for a while and thought that beanstalk was the solution for me. However, after about 4 hours talking to customer service on Bluehost I found out they were blocking Beanstalk’s IPs for some reason and informed me that – no – they would not unblock them for me. Alas! But at least now I won’t have to pay $15/mo for a service I can actually pretty easily do myself.

    Much appreciated!

  8. Ben says

    Hey, thanks very much for this write up…. its been a big help (as has your excellent Compass WP theme too!).

    I am wondering how do you deal with syncing the projects? For example you have pushed x amount of times and something is updated on the live site. How do you pull those changes from the live site. Live -> live repo -> local repo?

    Not sure this is covered in this article? Am I missing something obvious?

    • says

      If you’re talking code changes in the theme, in theory there wouldn’t be changes on the remote site. Worst case, I’ll just FTP the files back and git commit locally if a client has had another developer work on them that didn’t add things to the git repo. For the database, I usually just do a mysqldump and run a search and replace script to change all the URL paths, but I only do that if there’s content on the live site that needs to be styled or I need to work with it for a change.

      • Ben says

        Thanks Matt, yeah I am using the whole WP install not just the theme in the repo. As some of my more technically competent clients then update WP on the live site, I was looking for a way to pull the changes back into my local install.

        FTP’ing on the odd occasion to pull those down is not the end of the world but if I find a smoother resolution I will let you know. Thanks again.

  9. NIck Christensen says

    Awesome. I’m a little unclear though, why can’t you just push straight to the directory where you theme resides? You mention this distinction as being important more than once, but I couldn’t figure out why?

    • says

      Hi Nick,
      I hope this helps, the reason why wouldn’t push, at least as far as I understand it:

      1. You can only push to Bare Repos
      2. And most importantly, because this way you don’t expose all your previous versions cuz there is no .git folder

      Hope this helps! 😀

  10. says

    Finally! I think I’ve spent the better part of three days trying to get some form of version control working. This article did it. I’ll save Grunt for another day, but thanks for writing this out!

  11. says

    Say you have a custom theme and plugin(s) made for site; in this setup you would have separate git projects for each, right?

    • says

      Yup! I keep my themes and plugins separate so that I don’t have to deploy and push changes for both all the time, especially since it would kind-of require putting either the entire WordPress install or just the wp-content directory into the Git repository to be able to deploy properly.

  12. Aaron says


    What a wonderfully thoughtful and organized tutorial on this. From someone who is getting started with Git, this really explained a lot to me after reading through many other articles that weren’t so clear.

    Thank You!

  13. says

    Nice article! I would simply the step where you create the bare repo like this:
    ssh git init –bare repos/your-project.git

    I use a bit more sophisticated method with multiple branches for beta and production releases:

    That is, in my deployment I have and, so my post-receive hook checks the branch I pushed to in order to upgrade the dev or prod site. In my case the deployment directories are proper Git repositories, but you’re right, they don’t need to be. I’ll look into converting my prod deployment to use the GIT_WORK_TREE trick you did, but I’ll keep the dev site as a Git repo for convenience.

  14. francesco says

    It doesn’t work for me, When I push to the server it says all ok, but when I go in the /var/www/ folder it’s empty (i set 777 permission just to be sure, so it’s not a permission problem)

    I have followed all the steps multiple times and still no results.

      • dan says

        Did you figure this out? Im having the exact same problem. Everything seems to be ok (pushing to repo) but nothing is being deployed from after pushing repo. folder i empty.

        been trying to figure this out for so long, im about to give up.

  15. Guilherme Vaz says

    Hello Matt, great article! That’s exactly what I’ve come up with so far too, and it’s working great with GoDaddy linux shared hosting (though I thought it would’t).

  16. says

    Thanks for this Matt, I’ve only recently found your site but it’s just full of little gems like this. I didn’t even think git deploys were possible using Bluehost shared hosting!

    Set this up in minutes thanks to this great tutorial, only problems I had during setup came from failing to read steps thoroughly.

    You, sir… just saved me money!

  17. Tin says

    Hi Matt
    Great article. I have a question: If I already have a site up and running what would be the best way to start version control with that site?

    • says

      Hi Tin, the process is as Matt describes above. Navigate to the directory that you want to add to Git version control. Then run the following commands.

      git init
      git add .
      git commit -m "initial commit"

      n.b. this will add all the files and directories including sub directories to the Git repo.

  18. Ryan McCormick says

    That was easy! The only thing I had to add to be able to use just the “git push” was the –set-upstream (or -u): ‘git push -u myserver +master:refs/heads/master’ and now it works via ‘git push’.

  19. Matt T says

    Thanks for this tutorial, easy to understand and just what I was looking for.

    I’m getting an error on deployment though.
    stdin: is not a tty
    jailshell: git-receive-pack: command not found
    fatal: Could not read from remote repository.


Leave a Reply

Your email address will not be published. Required fields are marked *