WordPress,
the automated way

Michaël Perrin

  • Full-stack developer
  • PHP / Symfony, React, Elasticsearch, …
    and WordPress
  • michael_perrin

A typical WordPress set-up

aka. the "Famous 5-minute install"

…and more steps

  • Select the theme.
  • Install plugins.
  • Configure plugins in WordPress admin interface.
    • Set API keys (eg. MailChimp, Google Maps, etc.)
    • Import data
  • Create pages for the theme templates.

What are we going to automate?

  1. Infrastructure setup.
  2. WordPress configuration.
  3. WordPress data.

Benefits of automation

Why do we want to automate things?

Because developers are lazy? 🤔

  • Multi-environments (staging, QA, production, …)
  • Server configuration consistency.
    • Avoids « It works on my machine ¯\_(ツ)_/¯ ».
  • Easy versioning.
  • Continuous Integration and Deployment.
  • Streamline team work.
  • Faster and predictable deployments.
  • "Cloud-ready".

Automating infrastructure setup

Docker

Quick introduction on Docker

  • "Lightweight virtualisation".
  • Each service runs in a separate Docker container.
  • Many services already have official images.
  • Rule of thumb: one service per container.
  • Works on Linux, Windows, MacOS.

Docker: benefits

  • Very handy for local development and for servers.
  • Documents and versions the infrastructure of the project.
  • Allows to install any version of any dependency.
  • Easy to update configuration for all environmnents.
  • Isolation of projects.
  • Easy to deploy.

Containers for WordPress

Setting up Docker containers

docker-compose.yml file at the root of the project:

services:
  wordpress:
    image: wordpress:5.2-php7.3-fpm-alpine
    # ...

  database:
    image: mysql:5.7
    # ...

  webserver:
    image: nginx:1.14-alpine
    # ...
                    
```yml wordpress: image: wordpress:5.2-php7.3-fpm-alpine links: - database:mysql volumes: - wordpress_data:/var/www/html restart: always ```

database:
  image: mysql:5.7
  restart: always
  volumes:
      - db_data:/var/lib/mysql
  environment:
    MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    MYSQL_DATABASE: ${MYSQL_DATABASE}
    MYSQL_USER: ${MYSQL_USER}
    MYSQL_PASSWORD: ${MYSQL_USER_PASSWORD}
  ports:
    - 3306:3306
                    

                    database:
                      image: mysql:5.7
                      restart: always
                      volumes:
                          - db_data:/var/lib/mysql
                      environment:
                        MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
                        MYSQL_DATABASE: ${MYSQL_DATABASE}
                        MYSQL_USER: ${MYSQL_USER}
                        MYSQL_PASSWORD: ${MYSQL_USER_PASSWORD}
                      ports:
                        - 3306:3306
                    

                    database:
                      image: mysql:5.7
                      restart: always
                      volumes:
                          - db_data:/var/lib/mysql
                      environment:
                        MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
                        MYSQL_DATABASE: ${MYSQL_DATABASE}
                        MYSQL_USER: ${MYSQL_USER}
                        MYSQL_PASSWORD: ${MYSQL_USER_PASSWORD}
                      ports:
                        - 3306:3306
                    

                        webserver:
                          image: nginx:1.14-alpine
                          links:
                            - wordpress
                          volumes:
                            - wordpress_data:/var/www/html
                            - ./docker/nginx/website.conf:/etc/nginx/conf.d/default.conf:ro
                          ports:
                            - ${WEBSERVER_PORT}:80
                    

.env file


                        MYSQL_ROOT_PASSWORD=my_root_password
                        MYSQL_DATABASE=wordpress
                        MYSQL_USER=wordpress
                        MYSQL_USER_PASSWORD=wordpress_pwd
                        WEBSERVER_PORT=8000
                    

Environment variables

  • Specific to an environment. Examples:
    • Database parameters
    • Website URLs (eg. root URL of the website, API endpoint, etc.)
    • Email recipients
  • May contain sensitive data.
  • Can be set in:
    • A .env file
    • In CI/CD tools like Travis, TeamCity, etc.
  • Not commited in the project.

Project structure

Let's run it

One command to start Docker containers:
docker-compose up -d

All services are up and running:

+
## Adding services is easy Want phpMyAdmin? Update *docker-compose.yml*: ```yaml services: # ... phpmyadmin: image: phpmyadmin/phpmyadmin links: - database:db ports: - 8082:80 ``` Access it at http://localhost:8082

Automate WordPress configuration

Ditch the wizard

  • Set the language.
  • Add default admin user.
  • Configure database parameters.
  • Define posts URL structure.
  • Choose a theme.
  • Install plugins.

WP-CLI

WP-CLI provides a command-line interface for many actions you might perform in the WordPress admin.

wp-cli.org

Useful WP-CLI commands

Generate a wp-config.php file with DB parameters:
wp-cli config create --dbhost=… --dbname=… <...>
Run the standard WordPress installation process:
wp-cli core install --url=… --title=…
Install and activate plugins:

                            wp-cli plugin install contact-form-7 --activate
                            wp-cli plugin install advanced-custom-fields --activate
                        

How to install

Requirements for running WP-CLI:

  • A PHP installation.
  • Installing it (on every environment).
  • Access to the WordPress directory.

Let's use a Docker container!

WP-CLI in a Docker container

                        
toolbox:
  image: michaelperrin/wordpress-toolbox
  volumes:
    - wordpress_data:/wordpress
    - ./Makefile:/scripts/Makefile
  depends_on:
    - database
  environment:
    MYSQL_HOST: ${MYSQL_HOST}
    MYSQL_DATABASE: ${MYSQL_DATABASE}
    MYSQL_USER: ${MYSQL_USER}
    MYSQL_USER_PASSWORD: ${MYSQL_USER_PASSWORD}
    WORDPRESS_ADMIN_EMAIL: ${WORDPRESS_ADMIN_EMAIL}
    WORDPRESS_ADMIN_PASSWORD: ${WORDPRESS_ADMIN_PASSWORD}
    WORDPRESS_ADMIN_USER: ${WORDPRESS_ADMIN_USERNAME}
    WORDPRESS_DOMAIN_NAME: ${WORDPRESS_DOMAIN_NAME}
    WORDPRESS_WEBSITE_URL: ${WORDPRESS_WEBSITE_URL}
    WORDPRESS_LOCALE: en_US
    WORDPRESS_WEBSITE_POST_URL_STRUCTURE: "/%year%/%monthnum%/%day%/%postname%/"
    WORDPRESS_WEBSITE_TITLE: "My beautiful website"
                        
                    

Makefile

  • A file containing a set of scripts grouped as tasks.
  • Abstracts and groups commands.
  • The file is versioned in the project.

                    task_one:
                        # ...

                    task_two:
                        # ...

                    task_three: task_one task_two
                        # ...
                    
Run: make task_one

Makefile for our project


                        install: start configure install_plugins set_theme

                        start:
                            docker-compose up -d

                        stop:
                            docker-compose stop

                        configure:
                            # ... (several WP-CLI commands)

                        set_theme:
                            docker-compose run --rm toolbox wp-cli theme activate my-simple-theme

                        install_plugins:
                            # ... (one WP-CLI command per plugin)
                    

One command

make install
  • Create Docker containers for WordPress, MySQL and Nginx.
  • Initiate the WordPress database.
  • Configure WordPress:
    • Database parameters.
    • Default WordPress user.
    • Set language.
    • Set default URL structure.
  • Activate the custom `my-simple-theme` theme.
  • Install plugins (Contact Form 7 and WP MailChimp as examples).

Automate data changes

Data migration examples

  • Remove WordPress default content.
  • Add page and menu entries.
  • Configure plugins
    • Add a contact form (Contact Form 7)
    • Define MailChimp API key

WP-migrations plugin

  • Inspired by Doctrine Migrations for PHP and SQL
  • Runs user-defined migration files to describe data changes.
  • Embeds a WP-CLI command to execute migrations.

Anatomy of a migration


                    <?php

                    namespace Migration;

                    use WP\Migration\MigrationInterface;

                    class Migration201909221755 implements MigrationInterface
                    {
                        public function execute()
                        {
                            // ...
                        }
                    }
                    

Migration files

  • Stored in wp-content/migrations
  • Only executed once.
  • Should be committed to version control.
  • Executed on deployment.

Example


                        class Migration201909221755 implements MigrationInterface {
                            use MigrationUtilitiesTrait;

                            public function execute() {
                                $pageData = [
                                    'post_name'    => 'about', // Permalink
                                    'post_title'   => 'About us',
                                    'post_content' => '',
                                    'post_status'  => 'publish',
                                    'post_author'  => 1,
                                    'post_type'    => 'page',
                                    'menu_order'   => 1,
                                ];

                                $postId = wp_insert_post($pageData);

                                $this->addToMenu($postId, 'About us');
                            }
                        }

                    

Example 2


                        class Migration201905311454 implements MigrationInterface {
                            public function execute() {
                                $this->setMailChimpApiKey(getenv('MAILCHIMP_API_KEY'));
                            }

                            private function setMailChimpApiKey(string $key) {
                                $options = get_option('mc4wp');
                                $options['api_key'] = $key;
                                update_option('mc4wp', $options);
                            }
                        }
                    

Run the migrations

make wordpress_migrations_execute

Run the migrations, again

make wordpress_migrations_execute

Benefits of migrations

  • Structural changes are versioned.
  • Facilitate team work and easy to deploy.
    • No "in-between" state
    • Multi-environments (using environment variables)

Final tree structure

  • Only what our project needs.
  • Easy versioning.
  • Includes infrastructure & all automations.

Thank you!