Jump to Navigation Jump to Main Content Jump to Footer
Home » Docs » CI / CD

CI / CD

To automate the process of project building and deployment, Gitlab Pipelines or Github Workflows are recommended.

Gitlab: Basic Scenario

To establish a Gitlab pipeline you need to perform a few configuration steps and create a .gitlab-ci.yml file in your project’s root folder. Below you can find a sample configuration that may serve as a starting point of your implementation. It assumes a fictionary scenario of building a theme and deploying it to a development environment on WP Engine. It can be relatively easily adapted to other hostings, environments and more sophisticated pipelines.

  1. Add your public SSH key to WP Engine
  2. Configure variables in Gitlab
  3. Create .gitlab-ci.yml
  4. Optionallly, add .rsync-exclude file to configure exclusions from file synchronization

Sample .gitlab-ci.yml

stages:
  - build
  - deploy

build:
  stage: build
  image: node:20.17-alpine
  artifacts:
    paths:
      - wp-content/themes/name-of-your-theme
    expire_in: 1 hour
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - wp-content/themes/name-of-your-theme/node_modules/
      - wp-content/themes/name-of-your-theme/vendor/    
  before_script:
    - apk add --no-cache php php-json php-phar php-openssl php-mbstring php-simplexml php-tokenizer php-xml php-dom php-xmlreader php-xmlwriter curl
    - php -m
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
  script:
    # Build theme
    - cd wp-content/themes/name-of-your-theme
    - npm install
    - composer install
    - npm run build
  only:
    - develop
    - staging
    - master

deploy_to_wpengine:
  stage: deploy
  image: alpine
  before_script:
    - apk add --no-cache openssh-client rsync coreutils
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" | base64 -d > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan -H ${DEV_ENV}.ssh.wpengine.net >> ~/.ssh/known_hosts
  script:
    - rsync -avzP --timeout=300 --delete --delete-excluded --exclude-from=.rsync-exclude --no-perms --no-owner --no-group -e "ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no" wp-content/themes/ ${DEV_ENV}@${DEV_ENV}.ssh.wpengine.net:/sites/${DEV_ENV}/wp-content/themes/
  only:
    - develop
YAML

Let’s break down this file:

  1. We create two stages of the pipeline: build and deploy
  2. We add build job to the build stage. This job will trigger whenever we commit or merge to develop, staging or master branch (see only declaration). It will install all the necessary dependancies and cache some of them, then it will run the build script.
  3. We add deploy_to_wpengine job to the deploy stage. In this job we first authorize the connection (see before_script declaration) and then we sync our files. Here we assume, that the content of wp-content/themes folder will be synchronized, however we can either sync whole project or run multiple syncs, for example one for /themes and other one for /plugins. Please notice also, that this job will run only when the branch we are committing or merging to is develop
  4. There are three variables used in this script: ${CI_COMMIT_REF_SLUG}, $SSH_PRIVATE_KEY and ${DEV_ENV}. The first one is automatically supported by Gitlab, no need to set it. However, we need to set the private key and development environment variables. We do it here:

To set the variables you need to have maintainer access to the repo, however to use them developer is sufficient if set accordingly to the screenshot above. This example is from a live project, so the variables differ slightly from the ones in our sample discussed here. Nevertheless, the general idea is the same. We paste the private part of our SSH key as $SSH_PRIVATE_KEY variable and WP Engine’s environment name as ENV variable.

  1. Please observe also, that it is possible to exclude some files or folders from sync job. To do that, we can create a .rsync-exclude file.

Sample .rsync-exclude file

.github/
node_modules/
wp-content/themes/name-of-your-theme/src/
.editorconfig
.gitattributes
.gitignore
.jscsrc
.jslintignore
.travis.yml
package.json
package-lock.json
composer.json
composer.lock
YAML

Gitlab: More Advanced Usage

Multiple builds in one pipeline

Sometimes we have to run a few build jobs before deployment (like for example when we are developing a theme and plugins that also need building). In such situation:

  1. Add new path to artifacts
  artifacts:
    paths:
      - wp-content/themes/name-of-your-theme
      - wp-content/plugins/name-of-your-plugin
YAML
  1. Add new paths to cache if necessary
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - wp-content/themes/name-of-your-theme/node_modules/
	    - wp-content/themes/name-of-your-theme/vendor/
      - wp-content/plugins/name-of-your-plugin/node_modules/
YAML
  1. Run another build job as if you did that in the command line
  script:
    # Build theme
    - cd wp-content/themes/name-of-your-theme
    - npm install
    - composer install
    - npm run build
    # Build plugin
    - cd ../../plugins/name-of-your-plugin
    - npm install
    - npm run build    
YAML

Multiple rsyncs in one pipeline

We are not limited to one rsync per pipeline, in fact we can do as much as we need, the same as if we did that in the command line. We just need to add a new command in our deploy job, like so:

script:
    - rsync -avzP --timeout=300 --delete --delete-excluded --exclude-from=.rsync-exclude --no-perms --no-owner --no-group -e "ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no" wp-content/themes/ ${DEV_ENV}@${DEV_ENV}.ssh.wpengine.net:/sites/${DEV_ENV}/wp-content/themes/
    - rsync -avzP --timeout=300 --delete --delete-excluded --exclude-from=.rsync-exclude --no-perms --no-owner --no-group -e "ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no" wp-content/plugins/ ${DEV_ENV}@${DEV_ENV}.ssh.wpengine.net:/sites/${DEV_ENV}/wp-content/plugins/
YAML

The code above will sync both themes and plugins to the respective remote environment.


Manual deployment trigger when syncing to production

It is a good practice not to trigger the deployment stage automatically when merging to master/main branch that is synchronized with the production environment. We can add a manual trigger in such situation at the end of deployment job.

	[...]
  only:
    - master
  when: manual
YAML

With only declaration we instruct the pipeline that this particular job should be executed only if committed or merged to

master branch and with when we force manual job start.


Post deployment test

We can also set up a simple test or tests that will check if after the deployment the environment is working as expected. This example will test if the website is not throwing a fatal error after files synchronization.

  1. First we need to add another pipeline stage
stages:
  - build
  - deploy
  - test
YAML
  1. The we add a job to that stage
check_live_site_status:
  stage: test
  image: alpine
  before_script:
    - apk add --no-cache curl
  script: |
    RESPONSE=$(curl --max-time 30 -s -o response.html -w "%{http_code}" "${REMOTE_URL}")
    echo "HTTP Response Code: ${RESPONSE}"

    if [ "${RESPONSE}" -ne 200 ]; then
      echo "❌ ERROR: Website returned HTTP ${RESPONSE}"
      rm response.html
      exit 1
    fi

    if grep -qiE "Fatal error|Critical error" response.html; then
      echo "❌ ERROR: Found 'Fatal error' or 'Critical error'!"
      rm response.html
      exit 1
    fi

    rm response.html
  only:
    - master
  needs: ["deploy"]
YAML

Let’s break down this code.

  1. We need to set a variable in Gitlab that will hold our live site’s url ($REMOTE_URL).
  2. Then we’re capturing the connections and saving them as response.html file.
  3. We are scanning the file for the occurrence of specific phrases, like “Fatal error”. If those phrases are found, the script exits with error.
  4. If the test is passed, response.html is removed.
  5. With only we declare, that this job is to be run only on master branch
  6. With needs we declare, that deploy stage has to be successfully passed to start this stage.

Do you like Chisel?

Give it a star on GitHub!