Home » Code » How to deploy WordPress themes with Git

How to deploy WordPress themes with Git

Posted by on April 8th, 2013

How to deploy WordPress themes with Git
One of the legacy problems with WordPress is it does not offer an easy out of the box solution for using Git. This means that it is often the case that theme management is handled through FTP.

Using Git for deployment is far superior than FTP in just about every way. In this post I will show you why you should only ever deploy with Git, and how easy it is to set it up.

Why you should use Git?

I’ve already covered why you should use Git in the past, so I will keep this to a brief summary.

Git is a version control system that sits discretely in the background of your project and monitors any changes you make. After you make a change to your code you can commit your changes to Git which makes a new entry in your project’s timeline. This gives you the ability to go back in time and resurrect your code as it was at a certain point in time.

Git is also invaluable when you are working as part of a team of developers or when you are working on a project that has many concurrent development features or versions.

One of the big advantages of using Git is when it comes to deployment. As you probably already know, using FTP for deployment is not very good. FTP requires you to drag and drop files into their correct folders on the server. This means if you need to update a website that has files that are changed in many different folders, it becomes quite difficult. You also face the risk that an unsuspecting user will load a page that breaks because you haven’t finished uploading all of the new files.

Git allows you to manage deployment from the command line. This means you can push a new update and all of the files that are changed are instantly updated at the same time.

It also means that should you push a new update that critically breaks your website, it is really easy to rollback to the previous version. This would be almost impossible using regular old FTP.

So hopefully you can see the many advantages of Git when it comes to deployment. Again, Git will improve your workflow in a number of different areas which you can read more about.

How does Git deployment work with WordPress

There are many different ways you can handle Git deployment using WordPress. Personally, I only use Git for theme deployment, rather than having it manage my entire WordPress installation. This is because I don’t trust open source plugins enough to compromise my deployment. For example say a plugin makes a change to a file on the live server. The next time I push a deployment my files will overwrite this change.

If you do want Git to manage your entire installation, that’s fine. This is a good set up because you can update plugins locally to ensure nothing breaks before it ever hits the live server.

So the overview of how my Git deployment works is:

1. Local repository

Firstly I have my WordPress installation set up locally so I can develop themes and make changes without affecting the live site. Making changes to a live site is a really bad habit that you need to get out of. I have my Git repository set up in /wp-content/themes/my-theme so only the changes to my theme get managed.

2. Git repository on the server

Next I have a bare repository set up on my server. I like to keep all my repositories under /var/git, but you can keep them wherever you want really.

3. Live site files

And finally, I have my live site WordPress installation under /var/www/path-to-live-site as you typically would with any Linux set up.

So the workflow of making changes is:

Local changes
Firstly we make local changes and commit them to Git

Push to the server
Next we push the changes to the Git repository on the server

Automatically update
When new commits are added to the Git repository, it will automatically update the live theme.

It’s that simple. Easy integration with Git and no more FTP.

Let’s set everything up.

Setting up Git and WordPress

So as I’ve outlined above, there are three main stages to setting up Git to work with WordPress:

Local repository

So the first thing to do locally is simply create a new Git repository. I’m going to assume that you already have Git set up on your machine.

To create a new repository, go to the root of your theme and run:

$ cd wp-content/theme/my-theme
$ git init

This will create a new repository.

Next we need to add all the current files to the repository.

// Shows all unstaged files
$ git status

// Adds all files to the current commit
$ git add -A

// Commits the files with a message
$ git commit -m "Add all the things"

Now if you run git status again, there should be nothing left to commit.

Server repository

Next we need to create the server repository. SSH into your server and into the directory where you want to store your Git repositories.

All we have to do at this stage is to create a bare repository. Run the following command in your directory.

$ git init --bare

A bare repository is essentially the same as the local repository you just made but it doesn’t have a working tree. You should use bare repositories to push and pull from on a centralised server because it doesn’t have the complications of a working tree.

Now that we have a repository to push to on the server, we can add an origin to our local repository.

Go back to your local repository and run the following command, substituting your SSH login, server IP and path to the bare git repository you just made:

$ git remote add origin name@123.456.789:path/to/your/git

You should be required to enter your server password.

Origin is simply the main centralised repository. Even though all of your work originated from your local machine, the bare repository is still called the origin. This means that should you ever want to allow access to another developer, all they have to do is pull from the origin.

Next you can run the following command to push your code from your local machine to the server:

$ git push origin master

This is simply saying, push the master branch of the current repository to the origin.

Now if you have a look at the git log of the server’s repository, you should see that your code has been pushed up. Because this is a bare repository without a working tree, your code won’t actually be in the repository.

Automatic update

If you look at your live installation directory, you will notice nothing has changed. In order to automatically update the live files we need to set up a post update hook. This is simply a job that will be run whenever the git repository is updated.

Go into your bare repository, then into the hooks directory.

$ cd hooks

If you list all of the files in this directory you will see a set of sample files that you can use to create your hook.

$ ls -l

To create a post-update hook, simply create a new file and copy the following:

// Create a new file
$ sudo vim post-update

// Copy this text
export GIT_WORK_TREE=/path/to/you/live/files
git checkout -f

This will checkout your files into the directory your have specified every time the repository is updated.

Now if you go back to your local code, make a change, commit the change and push it to the origin you will see your code magically update. You can now deploy changes to your live site without ever having to touch FTP again.


And there you have it, simple Git deployment using WordPress. There are many ways you extend this. For example, you could deploy to a development version or staging version depending on which branch that you push to the origin. This would allow you to test your changes on the server before you deploy the code to the live site.

Hopefully that was a nice and easy introduction to using Git with WordPress. You can of course use the same technique for deployment of any website. I promise you, once you get away from using FTP, you will never look back.

Philip Brown

Hey, I'm Philip Brown, a designer and developer from Durham, England. I create websites and web based applications from the ground up. In 2011 I founded a company called Yellow Flag. If you want to find out more about me, you can follow me on Twitter or Google Plus.

  • benbuie

    I can’t get the hook to work. is the /path/to/my/live/files a new folder on my server? Or is it in the bare repository on the server?

    • Yeah, it would be where the actual website files on the server are, something like /var/www/project

      Make sure you have set the right file permissions. Whenever something like this doens’t work for me it’s always either file permissions or ownership

      • benbuie


        • Sam

          Were you able to solve that problem? I have the same issue, when I push changes from my local machine I don’t see those changes in the actual website on the server. I checked permission and even try giving 777 but nothing so far.

          • benbuie

            It has been too long, I can’t remember.

          • Is it pushing into the bare repository correctly?

          • Sam

            Yes it is, when I type “git push origin master” It shows tells me “Everything up-to-date” . However if I go to the remote server (origin) how do I see if all the files were actually uploaded to the directory I created for git?

          • Chinara James

            Yea the hook is not working for me either but in the meantime from your website dir you can run git pull to pull in changes from the bare repo

          • Sam

            Yes the hook is no working,.. Thanks for the top Chinara, I am pulling changes in that way. It is a bit more typing but works

          • Chinara James

            I got the hook working.
            Try chmod 755 post-update
            Confirm the permissions have been changed with ls -l
            Should see rwxr-xr-r– for post-update

          • Sam

            Thanks Chinara, .. it is still no working for me. I even tried chmod 777 but nothing so far.

          • Hmm, so that part is working correctly. The export to your live directory isn’t working. Is the path to your live directory correct?

  • Chinara James

    I got stuck where you add the origin remote. First it said remote origin already exists. So i did “git remote -v” which outputted:

    No url was next to either so I used “git remote set-url” to add the url you specified for origin. It seemed to work but when I use “git push origin master” I get an error:

    bash: git-receive-pack:command not found
    fatal:Could not read from remote repository.
    Please make sure you have correct access rights and the repository exists.

    I followed the instructions here to get git setup on my server:

    Any help is appreciated. Also do I “git init –bare” in the folder or do I create a project folder in the git folder and then run the command?

    • You need to do git init --bare in the repository on your server.

      Have you set up your SSH keys correctly or does it prompt you for a username and password?

      • Chinara James

        I believe the ssh keys are set correctly, it’s shared hosting so not a lot of flexibility. They basically set it up for you once you request and use your ftp credentials according to support.
        When I git push origin master I’m prompted for my password.

        I did git init – – bare in the git in the git folder on the server so my path to git is

        • Ah I see! It sounds like you don’t have permission to write to the git repo.

          You can should ask GoDaddy to change the permissions for user. This would be a two second job.

          You should checkout a VPS provider like MediaTemple or DigitialOcean though. It’s much easier to get this sort of thing up and running if you have ssh access to the server.

          Or actually, even better still, have you seen WPEngine? I’m sure they have Git set up for you as standard, so take a look at them too!

          Good luck :)

          • Chinara James

            OK, I’ll try. Yes I know those hosts. When the client’s hosting expires I’ll try to convince them of moving.

          • Chinara James

            Ok I got it to work, but it wasn’t a permissions thing. I had to permissions. I needed to setup up my config file to have these:

            git config –local –add remote.origin.uploadpack /git/bin/git-upload-pack

            git config –local –add remote.origin.receivepack /git/bin/git-receive-pack

            Just in case someone else needs it.

          • Ah nice one! Glad you got it sorted. I’ve never seen that before, but thank you for posting how you got it to work!

          • Hmm, I have never had to do that. Well at least you got it working :)

  • Nico

    How do you deal with local sourcfe files ? Let’s say we are using sass to build our theme css files. We want to keep them under local version control but don’t want them on the production server…

    • If you are going to be the only one working on the project you could just add the files or directory to your .gitignore file.

      If you want the files to also be under version control, but not pushed to production it gets a bit more complicated. Is that what you are trying to do?

      • Nico

        Yes Philip this is exactly what I’m struggling with … I still havent found a clean way to setup this workflow.

        • I’m not really sure about the best way to do it. Why don’t you want your Sass files to be on the production server?

  • Chinara James

    Is it normal that after you push origin master, if you run git status on the server side you see modified and untracked files.
    Does the hook only pull in files(working directory) but not update the git repository like git pull will?

    I was using git pull before to update on the server side.

    • You shouldn’t have a git repo in your www directory, if that’s what you mean?

      • Chinara James

        Yea that’s what I meant. I have one because the hook wasn’t working initially so I was using git pull which brought everything.
        Is there any drawbacks to deleting that .git folder now?
        Also if I need to clone, do I clone the bare repo and get the files?
        I know bare repos don’t have a working directory and thus no files.
        How does a team member clone to get the working files?


        • Yeah you shouldn’t have the git repo in the www directory. The drawbacks depend on where the latest version of your code is. If you have the latest version locally I would just delete it.

          Yeah you would clone the bare repo. All push and pull activity should happen on the bare repot, the git hook should then update the working directory.

          • Chinara James

            ok thanks much.

  • realdannys

    Phil, stupid newbie question here – but why do you have a separate place to upload the commits on the server to and then for the server to move them somewhere else? Why not merge steps 2 and 3 and have Git push the update files directly to the theme directory on your live site?

    • You don’t want your git repo to be publicly accessible. Only the files you want to serve to the internet should be in /var/www

      • realdannys

        Ah of course, obviously – I was over thinking it.

        If you don’t mind me picking your brains, once you’re up and running, do you update all plugins in your local version then push this to the repo? Do you bother to keep the databases in sync?

        We have site where we actually need to access the live version, however I want to turn wp-admin off on it so it makes it more secure, but more importantly, its on AWS so its a cloud instance rather than one web server it could be 2-200 depending on traffic. I thought git would be a perfect way to keep them all in sync by having them auto pull all updates from our master branch.

        However we need to login because we have user submitted posts to approve or deny, how would you go about that? Perhaps I have a local version which I can switch to “master” and it accesses the master database? Where we could in theory approve or reject posts? I don’t want there to be extra servers where possible (e.g. local version, a live test production server, and then our actual live web servers)

        One more thing, its me and friend working on the site, at the moment we’ve just been working on one version of the site live on a server and someone makes a change the other refreshes the site and says yay or nay. If we do it on our own local versions, what would you suggest the best way would be for one person to go “here look at this?” – would it be a developer branch and any pushed changes to it are automatically pulled down to the local version?

        So many ways to do the same thing! Would be really interested in your opinion.

        • Yeah you could update all of your plugins locally. It would prevent any broken plugin from messing up your live site.

          There are ways to keep your databases in sync. I think there is at least one plugin that does that. However it’s probably easier to just write a shell script to do the job.

          Hmm with the AWS thing, you could still have a wp-admin. As long as you change the admin username, set a password and limit login attempts you will be fine.

          To collaborate with someone through git you would just both pull from origin and then push and pull your feature branches, When you want to show you friend your work you would push your changes to origin and tell them to pull your branch and take a look at it. Git is design specifically for this so it’s really easy :)

          Hope that helps!

  • Deploying WordPress websites on WPEngine with git is as easy as `git push production master`. While behind the scenes they are using a post-receive hook similar to what you outline above, it’s already set up for you which is nice.

    They recently introduced the ability to administer your git developer keys through their web interface. Previously you needed to open a support ticket, and have a technician manually add your key to gain push access to the repo.

    This new feature makes working with git on WPEngine an even more powerful tool:http://wpengine.com/2014/02/06/wp-engine-introduces-git-administration-user-portal/

  • Malcolm

    I’m deploying the entire site including WordPress core using this method. Everything is working as outlined, except the WordPress core files are not getting pushed up to the live directory. It pushes the parent dir [ wordpress ] up, but it’s empty. Can’t seem to find anyone else who has experienced this issue… any advice is appreciated.

    • Hmm, are the files being tracked by git? Do you have the git repo in a subdirectory?

      • Chongo

        I am having the same problem. I used instructions from http://blog.g-design.net/post/60019471157/managing-and-deploying-wordpress-with-git which references your article here. At the end he mentions this problem. Do you have any techniques to truly ditch the FTP client and move core wordpress files when needed via git (or any other way?)

        • Hmm, yeah I haven’t really found a good way of doing that to be honest. That’s why for this example I only have my theme in git, not the whole core.

  • Tim Morris

    Thanks for the excellent article, I do look forward to seeing my FTP client in my rear-view mirror! I was able to get it working on my Bluehost VPS account when I used:

    chmod +x hooks/post-update

  • Tim Morris

    Ok, so another comment…I am a bit confused because it was working for me, then I removed the repository locally and on the server to redo it and make my own screencast for my future learning and it stopped working. The error I am getting is: “remote: error: pathspec ‘–f’ did not match any file(s) known to git.” I have started fresh in a completely different folder on my host and on my local installation, I read other people dealt with this problem because their .git directories were broken…but there is no .git file in any of my directories even with “ls -a”. The repository is receiving the files fine on the server, it shows in the log that it has the commits. Any thoughts?

    • Yeah that error means that you remotes are messed up. However if you are starting completely from scratch, there shouldn’t be any remotes.

      I would delete everything and completely start from scratch with a fresh git init repo.

  • Miguel

    After almost a year of this article I have to say thank you :D I just added what Tim Morris said and it works perfect now.

    • Good stuff :) Glad it helped!

      • Yeah everything went good, I appreciate it.

        I wanted to track the whole wp-content and I’ve got stuck trying to get a version of my website in a testing server hosted in the cloud. I wanted to be able to track any changes eg: client upload new content to uploads, etc.

        So what I did I just created another GIT repository and now I have a development version on my local, a testing version on the cloud and both are working with origin.

        That’s my whole story.


  • Git deployment is a much loved features of developers everywhere. Through Git, they have great control over pushing code whenever required.

    Source: http://www.cloudways.com/blog/managed-git-deployment/

  • Thanks for the tutorial Philip. I got it to work in the end, although not without some extra research and trial and error figuring out the correct remote repository URI that would work.

    The one that worked for me is more detailed than the example you gave. It is as follows:

  • Edgars

    Hi Philip. Thank you for your post, it is helpful for newbies like me to start things moving. I have read your tutorial along with several other. Your one works for me, though i had to find solutions to a row of small issues, while implementing.

    In your post you advice not to add the plugins into repository, because of 2 reasons – you don’t trust freesource stuff and you mention that a change may be done in plugins in the online version, and it may be then overwritten by the changes which we apply later from our local version by pushing it up on to server.

    I would like to ask you what exactly do you fear of in this case? If a change is done online, and we push local version up online, we will overwrite the online one, but still have a working version of the same plugin just with the files from local repository, or am i wrong here?

    Also, what happens if on local we have updated a plugin, which in turn has created a table or some new rows in our local DB. What will happen, when we deploy the changes in plugin to our online version? How exactly will the online DB get affected? Are there any issues possible?

    If you could comment a little bit on the previous 2 paragraphs of my post, it would help me understanding and choosing the best workflow for me. It is about plugin files and plugin DB entries. Something similar has been asked by user named Chongo earlier. Perhaps now you have come to any solution for this.

    • Thank you, glad you found it useful! :)

      To be honest I just don’t like how a WordPress installation can modify the files in the project. If I’m going to have those files under version control, I want the files in Git to be the one true source.

      If you were going to update the plugins locally you would need to get a copy of the database, update the plugins, push your code and update the database.

      So my solution is to only have the things I can control under version control. You might find something like this useful https://roots.io/bedrock/

      • Edgars

        Thank you for your answer. The link you provided is worth reading, and tools worth using.

        As for your approach in version-controlling only stable files of WP installation it is clear as well.

        I have only one question which i am still looking an answer for. In the workflow you suggest in your post – how do you initially get the full WP installation from server in to your local environment?

        • Hmm, I would probably just SCP the files down.

  • Cool! Moving from Rails dev with Heroku to WordPress I was shocked at how awful the workflow is with ftp and crap. So glad that I can use Git.

    And other resources show people using git for their entire site, but my idea was to use git for only the theme. Thank you for making me feel good about the decision, lol.

    Now I just have to figure out how to get a staging environment up which has been my biggest challenge so far. Good read sir, thank you.

  • How would you go with preprocessors, keeping in mind that we should not store build-files in version control?

  • Pingback: traiteur rabat()