How to setup WordPress deployment from GitHub Repository to a Web Server

Setting up a repository for your WordPress site is easy. First of all, you need to realize that you don’t want to version anything but your custom code. WordPress and the plug-ins you are using are already being versioned by their respective authors, and with automatic updates, you don’t want to end up with your own code being out of sync with your production server every time you do an update.

Also, you want to be able to keep your changes versioned and push them over to a staging and a production environment without having to ever pull changes from the server side.

To do this, your workflow needs to start from your local machine.
You need to have a local development environemnt setup to test your features and work on breaking changes. When you are satisfied with the functionality of your new code, you push the changes to a staging server, which is just a slave copy of your production server.

Here is an overview of what you need to do to setup this workflow. 

Requirements: you will need SSH access to the server and Git installed on it. Most providers offer these services nowadays as part of their hosting plans.

Step 1. Setup your git repository on github

You need to create a new repository, and only store into it your custom plugins and your custom child theme.

Leave out all the WordPress core and 3rd parties plug-ins: these are already versione eslewhere.

Clone the repository on your local machine.
I like to do this using GitHub’s desktop app.

Step 2. Setup a bare repository on your server.

SSH to your server, then create a directory to hold your repository and run:

git init –bare

I like to create one directory to hold the repositories in /home/username/repositories and I create one repo for the production environment and one for the staging environment.

Step 3. Setup your post-receive script.

On the server-side, go to the bare repository you have created in ./hooks/ and create a new post-receive script.

Be sure the script has execution privileges by doing:

chmod +x ./post-receive

My script does this every time it runs:

  • a. Destroys any existing checkout copy of the repository
  • b. Creates a new directory and does a fresh checkout of the repository
  • c. Goes into ./wp-content/themes, list all directories directly within it (not sub-sub-directories) and for each directory it will
  • remove the corresponding directory on the web root and copy it over from the freshly checked out copy.
  • d. Does the same process for ./wp-content/plugins.

This way, the script will remove any file that was removed from the git repository, while leaving intact all third parties themes and plug-ins installed in your main WordPress directory.

The script only does its magic if you are pushing to master. All other branches are ignored.

You can grab a copy of the script at the end of this post.

I like to do this process using an FTP client like CyberDuck and editing my post-receive script locally with Sublime Text, rather then using one of the Linux shell-based text editors.

Step 4. On your local machine, add a remote for your repository.

I recomment to keep a staging and a production repository that will deploy respecively to the staging environment and the live site.

To add the remote, you need to do:

git remote add {name} {protocol://[email protected]:PORT/path/to/repository}

i.e. git remote add staging ssh://[email protected]:1234/path/to/repository

Now you can deploy your code to the server by doing:

git push {name} master

i.e. git push staging master

Post-Receive Git Hook Script Sample

#!/bin/bash
while read oldrev newrev ref
do
    if [[ $ref =~ .*/master$ ]];
    then
        # Setup your script variables

        # 0 - Staging or 1 - Production (this is for messages only)
        IS_PRODUCTION="0" 

        # Your site domain (this is for messages only)
        DOMAIN="yourdomain.com"

        # Repository Directory (no trailing slash)
        REPOSITORY_DIR="/home/path/repositories/your_git_repository_name"
        
        # Web Root Directory (no trailing slash)
        WEB_ROOT_DIR="/home/path/public_html"

        # No more editing needed below this line

        # A temporary work directory, it gets created and removed
        TEMPORARY_DIR="${REPOSITORY_DIR}_checkout"

        if [ $IS_PRODUCTION == "1" ]; then
            echo "WARNING: DEPLOYING CHANGES TO PRODUCTION SERVER!"
        fi
        echo "${ENVIRONMENT} Environment: deploying master branch to ${DOMAIN}..."

        # Clean up checkout dir
        if [ -d "$TEMPORARY_DIR" ]; then
            # Control will enter here if $TEMPORARY_DIR exists.
            echo "Cleaning up temporary directory..."
            rm -rf "${TEMPORARY_DIR}"
        fi
        
        echo "Creating temporary git checkout..."
        
        # Create temporary directory 
        mkdir ${TEMPORARY_DIR}

        # Checkout a fresh copy of the repo
        git --work-tree="${TEMPORARY_DIR}" --git-dir="${REPOSITORY_DIR}" checkout -f
        
        # Get all sub-directories that exist in repository from themes
        cd "${TEMPORARY_DIR}"/wp-content/themes/
        pwd
        
        for D in `find . -mindepth 1 -maxdepth 1 -type d`
        do
            if [ -d "$D" ]; then
              # Control will enter here if $D exists.
              echo "Removing directory from ${WEB_ROOT_DIR}/wp-content/themes/${D}"
              rm -Rf "${WEB_ROOT_DIR}"/wp-content/themes/"${D}"

              echo "Copying checkout version of ${D} to ${WEB_ROOT_DIR}/wp-content/themes/"
              cp -aRf "${D}" "${WEB_ROOT_DIR}"/wp-content/themes/
            fi
        done

        # Get all sub-directories that exist in repository from plugins
        cd "${TEMPORARY_DIR}"/wp-content/plugins/
        pwd

        for D in `find . -mindepth 1 -maxdepth 1 -type d`
        do
            if [ -d "$D" ]; then
              # Control will enter here if $D exists.
              echo "Removing directory from ${WEB_ROOT_DIR}/wp-content/plugins/${D}"
              rm -Rf "${WEB_ROOT_DIR}"/wp-content/plugins/"${D}"

              echo "Copying checkout version of ${D} to ${WEB_ROOT_DIR}/wp-content/themes/"
              cp -aRf "${D}" "${WEB_ROOT_DIR}"/wp-content/plugins/
            fi
        done

        # Clean up checkout dir
        if [ -d "$TEMPORARY_DIR" ]; then
            # Control will enter here if $TEMPORARY_DIR exists.
            echo "Cleaning up temporary directory..."
            rm -rf "${TEMPORARY_DIR}"
        fi
    else
        echo "Ref $ref successfully received. Doing nothing: only the master branch may be deployed on this server."
    fi
    echo "Done!"
done