Sign in
Log inSign up
Using Composer with WordPress for easy Deployments

Using Composer with WordPress for easy Deployments

Vladimir Apostolski's photo
Vladimir Apostolski
·May 2, 2020

There are many ways to manage a WordPress application. Fortunately, it is possible to use Composer with WordPress.

In this article, we will show how to use Composer with WordPress, so that you can easily maintain it, manage it and deploy it in different server environments.

That means that you will be able to install WordPress core, themes, plugins etc. as well as update and delete them when needed via Composer.

To achieve this, we will use the following tools:

Using Composer with WordPress

Our first goal is to download the WordPress core, the plugins and themes as versioned Composer dependencies.

So in order to use Composer with WordPress, we will first install Composer and then create a composer.json file in the root directory of our project:

{
    "repositories":[
        {
            "type":"composer",
            "url":"wpackagist.org"
        }
    ]
}

Since Composer uses Packagist by default as a package repository, we will need to tell Composer that we will need WPackagist) instead.

Now we would be able to install public WordPress plugins and themes as Composer dependencies, for example like this:

{
    "require": {
        "wpackagist-plugin/akismet":"^4.1",
        "wpackagist-theme/twentytwenty":"*"
    }
}

Next, let's install the WordPress core via Composer. We will be using John P Bloch's mirror of WordPress Core to achieve that:

{
    "require": {
            "johnpbloch/wordpress": ">=5.4"
    },
    "extra": {
            "installer-paths": {
                "wp-content/plugins/{$name}/": [
                    "type:wordpress-plugin"
                ],
                "wp-content/themes/{$name}/": [
                    "type:wordpress-theme"
                ]
            },
            "wordpress-install-dir": "wordpress"
    },
    "repositories": [
            {
                "type": "composer",
                "url": "wpackagist.org"
            }
    ]
}

In the require section, we added the dependency. Next, in the extra section, we told Composer where to look for themes and plugins. Lastly, we defined the WordPress installation directory to be wordpress.

Now we can run the following command:

composer install --prefer-dist

Composer will now install WordPress within the wordpress directory in the root of our project.

To be able to fully manage WordPress with Composer, we need to use a different directory for wp-content instead of the default one, wordpress/wp-content.

Let's create a new directory in the project's root, called wp-content.

Let's go ahead and create the standard wp-config.php file and then add the following code:

$domain = 'mydomain.test';

define('WP_SITEURL', "{$domain}/wordpress");
define('WP_HOME',"http:{$domain}");

$httpHost =  isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $domain;

define( 'WP_CONTENT_DIR', dirname( __FILE__ ) . '/wp-content' );
define( 'WP_CONTENT_URL', 'http://' . $httpHost . '/wp-content' );

/** Absolute path to the WordPress directory. */
if ( !defined('ABSPATH') ) {
    define('ABSPATH', dirname(__FILE__) . '/wordpress');
}

/** Sets up WordPress vars and included files. */
require_once(ABSPATH . 'wp-settings.php');

Next, let's create an index.php file within our project's root directory:

<?php 
define('WP_USE_THEMES', true);
require( dirname( __FILE__ ) . '/wordpress/wp-blog-header.php' );

Since the wp-config.php file contains sensitive data, we will not commit it to our repository by creating a .gitignore file:

/wp-config.php
/wordpress/
/wp-content/
/vendor/

The wordpress, wp-content and vendor directories also need to be ignored, so we will add them to the .gitignore file as well.

Now we end up with a very simple project structure:

  • vendor/
  • wordpress/
  • wp-content/
  • composer.json
  • composer.lock
  • wp-config.php
  • .gitignore

Using composer to install WordPress plugins and themes from private repositories

You may want to install plugins or themes that are hosted in private repositories on Github, Bitbucket or somewhere else, but not on WPackagist. That is possible, but there are two things you need to do: 1) provide your credentials in an auth.json file 2) within the composer.json, in the repositories section, you need to tell Composer where to look for the repository:

{
            "type": "vcs",
            "url": "bitbucket.org/your-company/your-theme.git"
}

Using Envoy to deploy WordPress with Composer

Now that we have our WordPress project set up with Composer, let's see how to deploy it.

As we mentioned previously, we will be using Laravel Envoy to write a deployment script for WordPress.

The reason why we are using Envoy is because of simplicity. You can also decide to use a different tool like Deployer.

Let's go ahead and download Envoy.

With the deployment script, we can deploy our WordPress application to different servers: development, staging and production.

Because the project structure is so lightweight, now it will be easy to write the deployment steps.

Here is what our deployment script for WordPress will look do:

1) create a new release with a timestamp in a releases directory on your server 2) clone the repository 3) install all the dependencies 4) copy wp-config, .htaccess and other files specific to the server environment (e.g production) 5) create a symlink of the uploads directory to the new release 6) create a symlink of the new release to the domain's document root directory on the server 7) clean up old releases from the server.

First, let's go to our server and set up a directory called current as the domain document root directory on the server.

If you are not familiar that, you can follow a tutorial on how to do it.

You can create the following directory structure on your server: ~/sites/yoursite

Now, let's install Envoy with the following command:

composer global require laravel/envoy

Next, let's write the deployment script for WP. Let's create a new file in our project root directory called Envoy.blade.php.

In Envoy, we will use the following directives: @servers, @setup, @task and @story. It's very straightforward, here is what they mean:

  • @servers - this is where you define all your servers with their corresponding IPs.
  • @setup - section where you can define variables or configurations.
  • @task - a single action that should be executed on the specified server.
  • @story - a sequence of tasks that need to be executed on the specified server.

The way Envoy works is, it will ssh to the specified servers and execute the defined script, by following the directives.

Inside the directives you can use PHP to define which UNIX commands need to be executed on the servers.

The first step will be to define multiple server environments:

@servers(['local' => '127.0.0.1', 'staging' => 'w.x.y.z' 'production' => ['a.b.c.d']])

Next, let's define some variables for our setup steps. These are the things we can configure, depending on our needs.

In our case, we will assume we have a private repository on Bitbucket for the project.

The deployment script will clone the master branch and set up the releases directory structure, as discussed above.

@setup

    // the repository to clone
    $repo = ':your-company/your-wp-composer-project.git';

    // the branch to clone
    $branch = 'master';

    // set up timezones
    date_default_timezone_set('Europe/Berlin');

    // we want the releases to be timestamps to ensure uniqueness
    $date = date('YmdHis');

    // the application directory on your server
    $appDir = '~/sites/yoursite';

    // this is where the releases will be stored
    $buildsDir = $appDir . '/releases';

    // this is where the deployment will be
    $deploymentDir = $buildsDir . '/' . $date;

    // and this is the document root directory
    $serve = $appDir . '/current';

@endsetup

Next, let's create a task to actually create the directory for the new release:

@task('dir')
    echo "Preparing new deployment directory..."

    cd {{ $buildsDir }}
    mkdir {{ $date }}

    echo "Preparing new deployment directory complete."
@endtask

As you can see, it's using Blade syntax for the UNIX commands, with the variables we defined in @setup.

The following task will clone the repository and the specified branch:

@task('git')
    echo "Cloning repository..."

    cd {{ $deploymentDir }}
    git clone --depth 1 -b {{ $branch }} "{{ $repo }}" {{ $deploymentDir }}

    echo "Cloning repository complete."
@endtask

Of course, you will need to make sure your server can access the git repository.

The next task, will install the dependencies and copy the wp-config.php file:

@task('install')
    echo "Installing dependencies...";

    composer install --prefer-dist
    cp ../../wp-config.php ./wp-config.php

    echo "Installing dependencies complete."
@endtask

The next task will create the symlinks to the new release and to the uploads directory:

@task('live')
    echo "Creating symlinks for the live version..."

    cd {{ $deploymentDir }}
    ln -nfs {{ $deploymentDir }} {{ $serve }}
    ln -nfs {{ $appDir }}/uploads {{ $serve }}/wp-content/

    echo "Creating symlinks completed."
@endtask

The last task will perform a cleanup and delete old releases:

@task('deployment_cleanup')
    echo "Cleaning up old deployments..."

    cd {{ $buildsDir }}
    ls -t | tail -n +4 | xargs rm -rf

    echo "Cleaned up old deployments."
@endtask

We can configure the number of old releases we want to keep on the server. In our case it is 4.

Now let's write a @story directive where we can group the tasks we just wrote:

@story('deploy-staging', ['on' => 'staging'])
    dir
    git
    install
    live
    deployment_cleanup
@endstory
@story('deploy-production', ['on' => 'production'])
    dir
    git
    install
    live
    deployment_cleanup
@endstory

As you can see, we used the names of the tasks inside the @story to define the order of execution.

Finally we would be able to run the following commands to deploy our WordPress to the server:

envoy run deploy-staging

and

envoy run deploy-production

and so on, for each environment we want.

So now we can manage our WP installation completely via Composer.

Keeping WordPress in sync with Composer

As you already know, it is possible to update plugins via Wordpress Admin panel. So if the WordPress administrator updates a plugin manually, it will make the site out of sync with the Composer file, which will beat the purpose of using Composer and it might cause the website to stop functioning properly.

So, how to avoid this situation?

It will be best to tell administrators not to update plugins on their own, especially not in production.

Once you have that settled, you can write a simple task in Envoy to update a given plugin:

@task('update-plugin')
cd {{  $deploymentDir  }}
wp plugin update {{ $plugin }} --version={{ $pluginVersion }}
@endtask

@story('update')
update-plugin
@endstory

Here we are using WP-CLI to perform the update ( but you can also do it via the admin panel if you prefer )

You can execute it by typing:

envoy run update --plugin=bbpress --version=2.6.4

Here we assume we want to update the plugin bbpress to the version 2.6.4

Once this is executed, you can also update your composer.json:

composer require wpackagist-plugin/bbpress 2.6.4

And finally, let's deploy our change to the server:

envoy run deploy-production

Other ways to use Composer with WordPress

We have showed how to manage WordPress with Composer and how to deploy it with your own customizable deployment script.

There are, of course, other ways to manage WordPress sites with Composer. One of the popular ways to do so, is with Bedrock.

Bedrock is a WordPress boilerplate project, with pre-defined directory structure.

It looks like this:

├── composer.json
├── config
│   ├── application.php
│   └── environments
│       ├── development.php
│       ├── staging.php
│       └── production.php
├── vendor
└── web
    ├── app
    │   ├── mu-plugins
    │   ├── plugins
    │   ├── themes
    │   └── uploads
    ├── wp-config.php
    ├── index.php
    └── wp

Bedrock will help you move WordPress to a directory called wp and set up different environments: development, staging and production.

It also does a good job setting up config values in a .env file with the help of PHP Dotenv.

The wp-config.php reads the .env file which is not located in the web directory and that improves the security of the website.

wp-content is renamed to app

Once you have it set up, you can add plugins as Composer dependencies, similar to what we showed earlier.

For deployment, you can write a deployment script with either Envoy, Deployer or some other tool.

Conclusion - managing and deploying WordPress Sites with Composer

In this article, we showed two ways (manually and with Bedrock) of managing WordPress with Composer and a way how to easily deploy WordPress to multiple environments when it is used with Composer.

First we defined a composer.json file which enabled us to install the WordPress Core, as well as the themes and the plugins as dependencies.

This made the project very lightweight and easy to deploy with a script.

While WordPress is not initially thought to be used as a Composer dependency, it is a great advantage to use it in modern development workflows as it ensures fairly easy deployments with zero downtime and quick rollbacks.

If you have any experience using WordPress with Composer (and deploying it), we would like to hear about it in the comments.

Originally published at time2hack.com on April 27, 2020.