While researching WordPress auto-deployment techniques, I found plenty of guides written for particular hosting platforms, or involving Docker containers and Kubernetes. As a result, I put together this guide as a light-weight and simple approach for configuring WordPress auto-deployment with GitLab CI/CD.

Summary

GitLab is configured to open an SSH connection into the destination server, and install WordPress Core and plugins using WP CLI. Custom code is then copied to the destination server and build commands are executed (e.g. npm install) to compile any frontend assets.

GitLab will SSH into the destination server, install WordPress, and copy in any custom code/themes.

Existing Posts and settings in the WordPress database are not modified, and neither are files within wp-content/uploads. This process deploys code only.

Requirements

Aside from frontend build tools (e.g. npm and composer), the only backend dependency required on the destination server is WP CLI. However, two additional areas of configuration are required to support this process:

  1. Creating an SSH user and key on the destination server, and ensuring the user has the appropriate permissions to the website directory and any executables required during the build. This is discussed in the “Preparing the Destination Server for SSH Deployments” section below.
  2. Defining Project and CI/CD variables in GitLab, including SSH connection information used to execute the deployment. See the “Configuring the GitLab CI/CD Variables” section for more information.

The “Local Development” section below also contains steps for standing up a WordPress site locally for rapid development.

Sample Project Posted to GitHub

A sample project demonstrating this build process has been posted to GitHub. Directions and configuration instructions can be found in the README.md. The sample project deploys and builds a custom theme based on underscores.

The code for the sample project does not include any WordPress Core files, plugins, or content. It only contains custom theme code inside of wp-content/themes/myBlog. The rest of this guide will refer to this sample project in an attempt to explain steps in greater detail, and highlight opportunities for customization/expansion.

If incorporating this process into your existing site, please make sure to backup your WordPress database and uploaded content inside of ./wp-content. As part of each build, all files inside of the WordPress directory are removed to make way for a fresh install/deployment (except for files inside wp-content/uploads). It would be terrible if your content was deleted!

GitLab CI/CD Yaml Config

The crux of any GitLab CI/CD configuration is the .gitlab-ci.yml file. Below is the YAML file used for this deployment process, with an explanation of the various sections beneath.

 1stages:
 2  - deploy-stage
 3
 4variables:
 5  WORDPRESS_SITE_DIR: /var/www/html/blog
 6  WORDPRESS_THEME_NAME: myBlog
 7  WORDPRESS_PLUGINS: "ewww-image-optimizer google-sitemap-generator wp-sweep"
 8  NODE_BIN_PATH: /usr/local/nvm/node/v14.15.3/bin
 9  COMPOSER_BIN_PATH: /usr/local/bin/composer
10
11deploy-stage:
12  stage: deploy-stage
13  image: kroniak/ssh-client
14  script:
15    - chmod og= $STAGE_ID_RSA
16
17    # prepare destination server - clean destination directory except for /wp-content/uploads
18    - ssh -i $STAGE_ID_RSA -o StrictHostKeyChecking=no $STAGE_SERVER_USER@$STAGE_SERVER_IP "
19        find $WORDPRESS_SITE_DIR -mindepth 1 ! -regex '^$WORDPRESS_SITE_DIR/wp-content/uploads\(/.*\)?' -delete  || true"
20
21    # install wordpress and plugins
22    - ssh -i $STAGE_ID_RSA -o StrictHostKeyChecking=no $STAGE_SERVER_USER@$STAGE_SERVER_IP "
23        cd $WORDPRESS_SITE_DIR && 
24        wp core download --version=5.8.1 --skip-content &&
25        wp core config --dbname=$STAGE_WORDPRESS_DB_NAME --dbuser=$STAGE_WORDPRESS_DB_USER --dbpass='$STAGE_WORDPRESS_DB_PASSWORD' --dbhost=$STAGE_WORDPRESS_DB_HOST &&
26        wp plugin install $WORDPRESS_PLUGINS"
27
28    # deploy updated theme code to destination server
29    - ssh -i $STAGE_ID_RSA -o StrictHostKeyChecking=no $STAGE_SERVER_USER@$STAGE_SERVER_IP "mkdir -p $WORDPRESS_SITE_DIR/wp-content/themes/$WORDPRESS_THEME_NAME"
30    - scp -r -i $STAGE_ID_RSA ./wp-content/themes/$WORDPRESS_THEME_NAME $STAGE_SERVER_USER@$STAGE_SERVER_IP:$WORDPRESS_SITE_DIR/wp-content/themes/
31
32    # build theme frontend JS/CSS assets on destination server
33    - ssh -i $STAGE_ID_RSA -o StrictHostKeyChecking=no $STAGE_SERVER_USER@$STAGE_SERVER_IP "
34        export PATH=$PATH:$NODE_BIN_PATH:$COMPOSER_BIN_PATH &&
35        cd $WORDPRESS_SITE_DIR/wp-content/themes/$WORDPRESS_THEME_NAME && 
36        composer install &&
37        npm install"
38
39    # enable deployed theme and plugins
40    - ssh -i $STAGE_ID_RSA -o StrictHostKeyChecking=no $STAGE_SERVER_USER@$STAGE_SERVER_IP "
41        cd $WORDPRESS_SITE_DIR && 
42        wp theme activate $WORDPRESS_THEME_NAME &&
43        wp plugin activate $WORDPRESS_PLUGINS"
44
45    # ensure WordPress and plugins have permissions on content/themes/plugin dirs
46    - ssh -i $STAGE_ID_RSA -o StrictHostKeyChecking=no $STAGE_SERVER_USER@$STAGE_SERVER_IP "
47        chgrp -R www-data $WORDPRESS_SITE_DIR || true"

Build Variables

Lines 4-9 define some of the variables used through-out this build script. These include things like paths on the destination server to where the WordPress site should be deployed (WORDPRESS_SITE_DIR), the name of the custom theme (WORDPRESS_THEME_NAME), and a list of WordPress plugins that should be installed and enabled as part of the build (WORDPRESS_PLUGINS).

Notice the NODE_BIN_PATH and COMPOSER_BIN_PATH variables: these must point to the node and composer binary locations on the destination server (can be found by typing which node on Ubuntu). These binaries will be used to build frontend JavaScript and CSS assets for the custom theme once the code has been deployed (lines 33-37).

Note: other variables are used in the script, but are expected to be defined in the GitLab CI/CD area (see the “Configuring the GitLab CI/CD Variables” section below).

Build Script

Lines 14-47 contain the build commands executed inside SSH session on the destination server. Below is a description of what each section does, and ideas for how it can be expanded:

  1. Prepare destination server – Lines 18-19 – all files inside the WORDPRESS_SITE_DIR path are removed to make way for a fresh install/deployment, withe the exception that files located inside of wp-content/uploads will not be removed. Adding additional ! -regex '<new-regex>' directives to this line will prevent other paths from being removed.
  2. Install WordPress Core and plugins – Lines 22-26 – WP CLI is used to download and install the version of WordPress defined on line 24, as well as all plugins listed in the WORDPRESS_PLUGINS variable. Line 25 creates the wp-config.php file that includes the WordPress database connection information. See the “Configuring the GitLab CI/CD Variables” section below for more on setting the STAGE_WORDPRESS_DB_* variables.
  3. Deploy Custom Theme Code – Lines 29-30 – custom code located in wp-content/themes/myBlog is copied to the destination server. Additional scp commands can be executed here to deploy other code paths.
  4. Build Theme Frontend JS/CSS – Lines 33-37 – in this example, npm and composer commands are executed on the destination server to build JavaScript and CSS files required by the theme. This section can be expanded to execute additional build commands, or simply removed if no assets need to be compiled.
  5. Enable Deployed Theme and Plugins – Lines 40-43 – enables themes and plugins to ensure they are available for use on the WordPress site.
  6. Set Permissions – Lines 46-47 – ensures that all deployed files inside of WORDPRESS_SITE_DIR are owned by the www-group, and can be read and/or modified by the web server. This is required for uploading photos to WordPress’s Media Library, and to use certain plugins that write to the web directory. These lines may be removed or adjusted depending on your configuration.

Local Development

The sample project contains a local-dev-setup.sh script that was created to mimic the CI/CD deployment process locally, and to facilitate rapid development against a local WordPress instance. The recommended approach is detailed below. See the Local Development section of the README for more information.

  1. Checkout the git code repository into a local “code directory”.
  2. Create a separate “wrapper directory” for the local-dev-setup.sh script to deploy into.
  3. Configure the local web server to point to the “wrapper directory”.
  4. Configure the local-dev-setup.sh script by setting each of the variables at the top; make sure the SOURCE_DIR points to the “code directory” and the DEST_DIR points to the “wrapper directory”.
  5. Execute the local-dev-setup.sh script.

As part of the script, a symbolic link is created in the “wrapper directory” that points to the custom theme in the “code directory” – this enables changes to be made in the source controlled “code directory” and be immediately available in the “wrapper directory” and webserver. The local-dev-setup.sh script should only need to be run once, or anytime the WordPress version or plugins change.

Preparing the Destination Server for SSH Deployments

A user and SSH key must be created on the destination server to allow deployments over SSH. The following directive will create a new user named deployer on a Debian/Ubuntu instance:

1sudo adduser deployer

The deployer user then needs read/write access to the website directory (e.g. /var/www/html/myBlog), and the ability to execute any binaries used during the deployment (WP CLI, npm, and composer in the case of this example). This may be done by adding the user to existing groups that have these permissions, or by reinstalling any executables as the deployer user.

Finally, the commands below will create an SSH key for the deployer user. This key will be added as a GitLab CI/CD variable in the next section so that the deployment pipeline can open an SSH session into the destination server as this user, and execute the deployment.

1# recommended to su into the deployer user to simplify housekeeping
2su deployer
3ssh-keygen -b 4096
4cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

Note: the key generated here will be save in ~/.ssh/id_rsa, and can be output with cat ~/.ssh/id_rsa.

Configuring the GitLab CI/CD Variables

The following configuration is required to prepare GitLab to perform the auto-deployment detailed in this guide:

  • A GitLab project must be created and have the sample code mentioned above committed. This project is referred to as wordpress-gitlab-ci below.
  • GitLab must have at least one Runner configured – more information available here.
  • The GitLab project must enable the Runner in the Settings > CI/CD > Runners section.

Once these steps are completed, project pipelines will be viewable from within the project’s CI/CD > Pipelines menu.

The next part of the GitLab configuration deals with setting CI/CD Variables required for the wordpress-gitlab-ci project. Once set, these variables can be read from within the gitlab-ci.yaml file.

Inside the GitLab Project > Settings > CI/CD > Variables section, create the following variables:

  • STAGE_SERVER_IP – contains the IP address of the destination server. This is the IP address is used by the GitLab Runner to make an SSH connection to the destination server.
  • STAGE_SERVER_USER – contains the user used when opening the SSH session. We created the deployer user in the “Preparing the Destination Server for SSH Deployments” section above.
  • STAGE_ID_RSA – contains the SSH private key used during the SSH session. For the deployer user, this key can be found in ~/.ssh/id_rsa. When entering this value in GitLab, make sure the Type is set to “File”, and a newline is created after the -----END OPENSSH PRIVATE KEY----- at the bottom.

Available in the wordpress-gitlab-ci project’s Settings > CI/CD > Variables page

Next, variables are defined that specify database connection information. These variables are created in the same CI/CD Variables page in GitLab, and are used to tell the auto-deployed WordPress site how to connect to the database on the destination server.

  • STAGE_WORDPRESS_DB_HOST – WordPress database host, e.g. localhost or 127.0.0.1 if the database is hosted on the same server as the web server.
  • STAGE_WORDPRESS_DB_NAME – WordPress database name on the destination server, e.g. wp_blog.
  • STAGE_WORDPRESS_DB_USER – WordPress database user.
  • STAGE_WORDPRESS_DB_PASSWORD – WordPress database user password.

This concludes the configuration inside of GitLab. Reminder to double-check the variables defined at the top of the .gitlab-ci.yaml file, described in the “Build Variables” section above.

Automatic Deployments

Once the configuration and setup is complete, GitLab CI/CD will build and deploy the WordPress site automatically after each commit. When viewing the pipeline in GitLab, the deploy-stage job will show as “passed”.

Auto-deploying the WordPress site following this process may break support for Pretty Permalink directives contained in .htaccess files. Below is an example of declaring these directives as part of an Apache vHost config file instead. The most important directive is the AllowOverride statement, which instructs the server to use the rules defined in the vHost config rather than the .htaccess file.

 1<VirtualHost *:443>
 2    ServerName taylor.callsen.me
 3    ServerAlias d3fssydaxhdo4e.cloudfront.net
 4
 5    DocumentRoot /var/www/html/wpSite
 6
 7    # other directives removed for brevity
 8
 9    #
10    #   WordPress Pretty URLs
11    #
12    <Directory /var/www/html/wpSite>
13        AllowOverride None
14        <IfModule mod_rewrite.c>
15            RewriteEngine On
16            RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
17            RewriteBase /
18            RewriteRule ^index\.php$ - [L]
19            RewriteCond %{REQUEST_FILENAME} !-f
20            RewriteCond %{REQUEST_FILENAME} !-d
21            RewriteRule . /index.php [L]
22        </IfModule>
23    </Directory>
24    # re-enable override to allow theme specific htaccess rules
25    <Directory /var/www/html/wpSite/wp-content/themes/myBlog>
26        AllowOverride All
27    </Directory>
28
29    # other directives removed for brevity
30
31</VirtualHost>