WordPress CI/CD – Deploy to WP Engine with GitHub Actions

I’m a huge fan of automating recurring processes and connecting all the things. Continuous integration and continuous deployment are just a couple of many secret crushes of mine. 

As a freelancer I get to see different ways and tools for handling these processes and I’m happy to say that my FTP client hasn’t been used for a quite long time. Recently I’ve got my hands on GitHub Actions workflows and it feels like a candy store. Options are endless but I’ll try to focus on single tasks to make life a little bit easier and, I’d dare to say it, a lot more fun.

TL;DR

  • WP Engine setup – Create new “Developer” in “Git Push” settings. Pair of SSH keys needed, only public will be used for settings.
  • GitHub Secrets setup – Create repository secrets. Both of SSH keys and WP Engine’s environment needed.
  • GitHub workflow setup – Create GitHub workflow file with 3 steps: Checkout repo, Fetch and unshallow and Push to WP Engine.
  • Testing workflow – Test and enjoy.

GitHub Actions based workflows are not a new thing but with other tools being a budget item or changing their pricing models, while GitHub Actions is free and not so complicated to configure, we see more projects making the switch and utilising its runners. 

So, let’s create that deploy WordPress to WP Engine workflow using GitHub Actions.

WP Engine setup

WP Engine offers GIT as a versioning control system, which can, of course, be used to deploy your code directly from your local to their environment. It has a setting called “Git push” where you can add your public SSH key. 

If you already have a pair of SSH keys on your machine you can use those but you can also generate a fresh pair. I’ve developed a habit of having a dedicated pair for each service which I’m not going to advocate as a good practice. It’s just a habit, nothing more. 

A note here is that once you add your public SSH key and connect it with a “Developer name” in WP Engine settings, this developer name and key are connected throughout the entire WP Engine system. You can use them over and over again in any environment, owned by different accounts, but they have to go in pairs. For me as a freelancer, having different clients hosting their sites on WP Engine, this meant not to include the project name, or anything project related, in the “Developer name” setting (yes, I do have two pairs of SSH keys on my machine just for WP Engine).

Once you add your “Developer name” and public SSH key to the “Git push” setting, the WP Engine setup is finished. Your user will be active in about 10 minutes (usually less) and you can check if activation happened by running this command in your terminal (doesn’t matter what is your current working directory):

$ ssh git@git.wpengine.com info

The output will show you all the environments your user has read and write access to. It’ll look something like this:

hello <developer_name> 
  R W production/<environment_name> 
  R W staging/<environment_name>

Useful links:

GitHub Secrets setup

We want GitHub to behave as your own local repo so it needs to have both, public and private SSH keys. As this is rather sensitive info, you want to make sure no one can get it and, luckily, GitHub introduced something called Encrypted secrets

Secrets can be used for any data you want: authorisation keys, tokens, passwords.. And they can be set for the repository, repository environment or the whole organisation. Not all options are available to all kinds of accounts. Also, there are some limits in number (100 per repository / 1000 per organisation) and size of secrets (64 KB) with workarounds for larger secrets. 

Here is more detailed info on how secrets work and how to use them properly, and especially when using third party actions

What we need here is a repository based secret times 3. Go to your repository Settings (you need admin access) > Secrets and hit New repository secret button.

A secret contains the name and the value. Naming has several rules:

  • Contains alphanumeric characters and underscores.
  • Must not start with GITHUB_ prefix nor a number.
  • Secret names are NOT case-sensitive. 
  • Must be unique at the level they are created at.

The names I went with have two goals:

  • To be descriptive enough so that I know exactly what they hold.
  • To be general enough so that I can reuse them in other repositories without changing them in my workflow files.

My secrets names are:

  • WPE_SSH_KEY_PUBLIC – this is content of the same key we set in WP Engine settings.
  • WPE_SSH_KEY_PRIVATE – this is the content of the private pair from the public key above.
  • WPE_ENVIRONMENT_NAME – this is WP Engine’s environment name which you get in terminal output when checking if your GIT user is active – R W production/<environment_name>. If you’re using the same GitHub repository for all 3 WP Engine’s environments (production, staging and development) it’s worth having secrets for each and naming them accordingly (WPE_ENVIRONMENT_PRODUCTION, WPE_ENVIRONMENT_STAGING and WPE_ENVIRONMENT_DEVELOPMENT).

We can now use these secrets in repository files with following syntax:

${{ secrets.WPE_SSH_KEY_PUBLIC }} 

Useful links:

GitHub workflow setup

Workflow is a set of events, jobs, and steps stored in a .yml file. Once the event from this file is triggered (such as pull request or push), GitHub runners will run steps found in it. These steps can be an action or command for command-line programs you run in the system’s shell or just printing a message for the output… You can set as many steps as you wish but the more you add the whole process will take more time. I generally prefer simplifying things. 

Actions are doing a single task in workflow (for example, push code from repository to some remote server). There are many actions created by the GitHub community that are already available and you can also create your own.       

There are many starter workflows available but we are going to create a custom workflow which will use a couple of actions. 

There are two ways to create a workflow file:

  1. In the root of your repository create a folder .github/workflows/ and there create an .yml file which can be named anything you want. I like descriptive names: deploy-wpengine–production.yml
  2. Go to “Actions” from your repository menu and follow “set up a workflow yourself” link. This will automatically create a new .yml file in .github/workflows/ folder. This file will already have a workflow template which can be helpful.

There’s a certain minimum of configuration needed for this file to be used as a workflow. There’s extensive documentation for all the options and syntax. 

Name

Name of the workflow is optional but useful if you have more than one workflow set for the repo. You’ll see it listed in the Actions tab.

So, this goes first in my deploy-wpengine–production.yml file:

name: [Production] WP Engine deploy

Event

This is mandatory information. We need to specify the event that will trigger workflow to run. There are many events you can set workflow for, for continuous deployment we’ll use push to production branch.

on:  
  push:    
    branches: [ production ]

Jobs

This is the place to define what needs to be done, where and in which order. You can set more than one job if needed. 

So first, we need a name for the job, I call it build. 

Then we define the runner for this job. This is also mandatory info. You can use self-hosted runners or GitHub’s ones. We’re gonna use GitHub’s ubuntu-latest.

jobs:
  build:
    runs-on: ubuntu-latest

Steps

And now we finally came to the actual work. Steps are defined in order we want them executed. Each step has a number of arguments:

  • id
  • if
  • name
  • uses
  • run
  • with
  • with.args
  • with.entrypoint
  • env
  • continue-on-error
  • timeout-minutes
Step 1: Checkout repo

First step is to checkout our repo so that workflow can access it. We’ll use community made action for this: actions/checkout@v2. There’s no configuration needed, just add a step, name it and define the action it uses.

steps:
  - name: Git checkout
    uses: actions/checkout@v2
Step 2: Unshallow source branch

When I was configuring workflow, this second step came in last. I kept getting this error:  

! [remote rejected] production -> master (shallow update not allowed)

WP Engine will not allow shallow updates so we need to unshallow the source repository. We’ll use the run command for this step. I hope you’re starting to see the endless possibilities that can be cosily snuggled into run command.

steps:
    - name: Git checkout
      uses: actions/checkout@v2
    - name: Git fetch and unshallow
      run: git fetch --prune --unshallow
Step 3: Push to WP Engine

The last step is to actually push our code to WP Engine. There are a number of available actions for this. I went with jovrtn/github-action-wpengine-git-deploy for its simplicity. This action has been forked more than a few times and some of these forks add options for more complex and advanced workflow. If you need something like that I’d recommend looking into epogeedesign/github-action-wpengine-git-deploy.

This action will use our repo secrets for environment:

env:
    WPENGINE_ENVIRONMENT_NAME: ${{ secrets.WPE_ENVIRONMENT_NAME }}
    WPENGINE_SSH_KEY_PRIVATE: ${{ secrets.WPE_SSH_KEY_PRIVATE }}
    WPENGINE_SSH_KEY_PUBLIC: ${{ secrets.WPE_SSH_KEY_PUBLIC }}

Here you can also specify the local branch from which the code will be pushed. It is necessary only if you’re using different branches for triggering workflow and pushing changes (e.g. when code is pushed to staging branch push to WP Engine’s development environment from development branch – this can work for setting updates to multiple environments with only one workflow).

Example:

on:
  push:
    branches: [ staging ]
  

    steps:
    
        env:
          WPENGINE_ENVIRONMENT_NAME: ${{ secrets.WPE_ENVIRONMENT_DEVELOPMENT }}
          WPENGINE_SSH_KEY_PRIVATE: ${{ secrets.WPE_SSH_KEY_PRIVATE }}
          WPENGINE_SSH_KEY_PUBLIC: ${{ secrets.WPE_SSH_KEY_PUBLIC }}
          LOCAL_BRANCH: development

The last step looks like this:

steps:
  - name: Git checkout
    uses: actions/checkout@v2
  - name: Git fetch and unshallow
    run: git fetch --prune --unshallow
  - name: Push to WP Engine
    uses: jovrtn/github-action-wpengine-git-deploy@0.1.2
    env:
      WPENGINE_ENVIRONMENT_NAME: ${{ secrets.WPE_ENVIRONMENT_NAME }}
      WPENGINE_SSH_KEY_PRIVATE: ${{ secrets.WPE_SSH_KEY_PRIVATE }}
      WPENGINE_SSH_KEY_PUBLIC: ${{ secrets.WPE_SSH_KEY_PUBLIC }}
The workflow file

Now we have a complete workflow file.

# deploy-wpengine--production.yml
name: [Production] WP Engine deploy
on:
  push:
    branches: [ production ]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Git checkout
        uses: actions/checkout@v2
      - name: Git fetch and unshallow
        run: git fetch --prune --unshallow
      - name: Push to WP Engine
        uses: jovrtn/github-action-wpengine-git-deploy@0.1.2
        env:
          WPENGINE_ENVIRONMENT_NAME: ${{ secrets.WPE_ENVIRONMENT_NAME }}
          WPENGINE_SSH_KEY_PRIVATE: ${{ secrets.WPE_SSH_KEY_PRIVATE }}
          WPENGINE_SSH_KEY_PUBLIC: ${{ secrets.WPE_SSH_KEY_PUBLIC }}

Useful links:

Testing workflow

To me, the easiest way to test workflow was to: 

  • create new branch from production branch, 
  • add something to main comment in style.css, 
  • push code to GitHub, 
  • create pull request to production branch, 
  • merge and 
  • watch the show.  

If everything went well during the yellow spinner animation, you’ll see the green checkmark. When I saw it for the first time I felt like I built the machine out of cutlery and pots and this machine just produced a cup of the most perfect espresso. Little things, eh?

Just remember to also check the actual file on your production site to make sure changes are there (and you didn’t break anything).  

I hope you enjoyed this little DevOps adventure, happy deploying and avoid Fridays for running workflows on production. Cheers! 

2 opinions

Leave a comment

Your email address will not be published.
You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

This site uses Akismet to reduce spam. Learn how your comment data is processed.