Thanks to limited documentation, I found setting myself up to use cPanel and Git to deploy to my shared linux hosting plan pretty damn tricky. So with the hope of saving someone else some time I’ll share how I got there in the end.
My goal was to replicate the workflow I’d got used to using with WP Engine:
- A staging site and a production site hosted with WP Engine.
- A local copy managed using Git. Always at least two branches –
master
reflecting what is currently on production, anddevelop
being what I’m working on. I only track the foldersthemes
,plugins
andlanguages
withinwp-content
, plus the.htaccess
file at the root. - Once happy with changes on local, I push the
develop
branch to staging and check everything’s working as it should on the WP Engine environment. - If all’s OK, then I merge
develop
into master on local and pushmaster
to production.
WP Engine set-up and documentation is excellent, so setting all that up is really no problem.
Other than WP Engine I also use a shared Linux hosting package with GoDaddy where I host lower traffic sites, and in cPanel I discovered a Git Version Control section. Documentation links point you to the cPanel documentation, which can hardly be called exhaustive. The principles I think are essentially the same as the WP Engine approach – in that you have a remote repository with your host, and every time you push to that repo the changes are then deployed on your host’s server. Do read the cPanel documentation though, and probably first – as it gives you the background against which to read this post.
In my case, I have multiple WordPress sites hosted on my account. For example, there’s this site as the main one in the public_html
folder, then I have a sub-domain of https://sandbox.sneezingtrees.com, and the staging copy of this site is hosted at https://sandbox.sneezingtrees.com/sneezingtrees-staging. The staging copy has the “Discourage search engines from indexing this site setting” activated – otherwise it’s pretty much identical.
The set-up I was after looks roughly like this then.
Overall then, here are the steps I went through:
- Create
.gitignore
on local respository so that onlythemes
,plugins
andlanguages
folders are tracked. - Create empty repositories on the host using cPanel.
- Get git push working.
- Set up deployment.
Create .gitignore
Here’s the .gitignore
file I use. The cPanel yml file we’ll come to in a sec – that’s the one that governs the deployent – and the custom maintenance file I’ll explain later too.
# Ignore everything except:
# - top-level .htaccess
# - .gitignore
# - cPanel yaml file
# - Custom maintenance file
# - plugins, themes and languages folders in wp-content
/*
!.htaccess
!.gitignore
!.cpanel.yml
!wp-content/
wp-content/*
!/wp-content/maintenance.php
!/wp-content/plugins/
!/wp-content/themes/
!/wp-content/languages/
Create empty repositories on the host using cPanel
This is the most straightforward step, done following the cPanel instructions. I just created two empty repositories, one in gitrepos/sneezingtrees
and one in gitrepos/sneezingtrees-staging
. (For avoidance of doubt the gitrepos
folder didn’t exist – I created it using the cPanel file manager.)
Get git push working
Since the url to access the repository is SSH (at least for my GoDaddy host it is), I needed to get SSH access sorted. No real problem for that just following GoDaddy documentation. (The result is that when I do a git push I am prompted for a password.)
Adding the remotes was straightforward. I used these two commands to add my production and staging remotes:
git remote add staging <staging-repo-URL>
git remote add production <production-repo-URL>
Then according to the cPanel documentation I should be able to make my first push with git push production HEAD
, but I hit these errors…
fatal: bad config value for 'receive.denycurrentbranch' in ./config
fatal: Could not read from remote repository
I found the solution in this Stack Overflow question, and essentially it was to amend the push command to this:
git push production HEAD --exec=/usr/local/cpanel/3rdparty/bin/git-receive-pack
That worked, and looking in the Git Version control section of cPanel I could see the commit and that the currently checked-out branch was master
. So from then on I could use the command git push production master
to push.
I then took similar steps to push to my staging repository. Then to push my develop branch to the staging repo I use the command git push staging develop:master
.
Set up deployment
This was the trickiest part for me. The cPanel document is extremely limited and basically gives you these two examples:
---
deployment:
tasks:
# Example 1 - deploys the index.html and style.css files to the example account’s public_html directory
- export DEPLOYPATH=/home/user/public_html/
- /bin/cp index.html $DEPLOYPATH
- /bin/cp style.css $DEPLOYPATH
---
deployment:
tasks:
# Example 2 - copies images directory and contents to the example account’s public_html directory
- export DEPLOYPATH=/home/example/public_html/
- /bin/cp -R images $DEPLOYPATH
Essentially everything after the tasks:
line is a series of BASH commands to be executed, so I just needed to figure out what commands to use to for my use case.
I had two problems.
First, the examples only show how to copy, not to sync, which is of course what I needed to do. What’s the use of copying over modified or new files if you aren’t also deleting files that are no longer present in your repository?
And second, how do I use a single yml file to deploy to one folder if it’s my staging repository, and deploy to another folder if it’s my production repository?
Lastly, there was a little flourish I wanted, which was to put my sites into WordPress maintenance mode while deploying.
I’ll take each of these in turn, but first here’s the yml file I ended up with.
---
deployment:
tasks:
# Get path data from config file (sets REPOPATH, DEPLOYPATH).
- source ./deploy-config.cfg
# Set RSYNCPATH. (NB - [username] is a placeholder for account username.)
- export RSYNCPATH=/home/[username]/bin/rsync
# Enable maintenance mode.
- /bin/mv ${DEPLOYPATH}/.maintenance-disabled ${DEPLOYPATH}/.maintenance
# Sync the wp-content folders and .htaccess file from the repo.
- rsync --rsync-path=$RSYNCPATH -a --delete-after ${REPOPATH}/wp-content/languages/ ${DEPLOYPATH}/wp-content/languages
- rsync --rsync-path=$RSYNCPATH -a --delete-after ${REPOPATH}/wp-content/plugins/ ${DEPLOYPATH}/wp-content/plugins
- rsync --rsync-path=$RSYNCPATH -a --delete-after ${REPOPATH}/wp-content/themes/ ${DEPLOYPATH}/wp-content/themes
- rsync --rsync-path=$RSYNCPATH -a --delete-after ${REPOPATH}/.htaccess ${DEPLOYPATH}/.htaccess
# Disable maintenance mode.
- /bin/mv ${DEPLOYPATH}/.maintenance ${DEPLOYPATH}/.maintenance-disabled
Syncing rather than copying
The command to use is rsync
, and you can see what the various options do here. Basically I just sync each of the plugins, themes and languages folders (and the .htaccess file) in the repository location with the same folders in the deployment location.
The only wrinkle I had was getting the rsync
command to work. The problem and solution I found in this Stack Overflow question, and it explains why I set the --rsync-path
option in the yml file.
While we’re on figuring out why things aren’t working, you should at least get a message in your terminal that tells you where log files related to the yml file execution are being updated on your server. Looking at those gave me the info I needed to find fixes.
Deploying to different locations using a single yml file
The problem and most of the answer is set out here, by someone trying to overcome the same difficulty. Instead of specifying $REPOPATH
and $DEPLOYPATH
in the yml file, we import the variables from a config.cfg
file that we reference relatively. That way, we can have different versions of the config.cfg
file stored in our remote production and staging repositories. The production version of the config.cfg
file looks like this:
DEPLOYPATH="/home/[username]/public_html"
REPOPATH="/home/[username]/gitrepos/sneezingtrees"
Just one thing to be aware of if, like me, your local machine is Windows: make sure you use LF line endings rather than CRLF on your config file otherwise you’ll get errors when your host Linux machine tries to read it.
Enabling and disabling maintenance mode
The basics of standard and custom maintenance files can be found here. My approach was to create a .maintenance-disabled
file at the root of each of my installations, then simply rename it to .maintenance
before deployment using the Linux mv
command (and rename it back to .maintenance-disabled
when deployment is over).
Conclusion
Hopefully I’ve given you enough clues to sort out a similar set-up on your Linux host. No doubt there are ways I could optimize the way I’ve done things, but honestly after all the head-scratching and frustrations I’d been through to get it working, I was just happy to move on to something else!