Using Composer with WordPress for easy Deployments
Tags: wordpress, composer, php, tutorial
Published: 2020-04-29 11:13:18

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: 
* [Composer](https://getcomposer.org) - for managing packages 
* [WPackagist](https://wpackagist.org/) - repository for WordPress plugins and themes
* [Laravel Envoy](https://laravel.com/docs/master/envoy) - for writing easy deployment scripts

## 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:

```json
{
    "repositories":[
        {
            "type":"composer",
            "url":"https://wpackagist.org"
        }
    ]
}
```

Since Composer uses [Packagist](https://packagist.org/) by default as a package repository, we will need to tell Composer that we will need [WPackagist]((https://wpackagist.org/)) instead. 

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

```json
{
    "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](https://github.com/johnpbloch/wordpress) to achieve that:

```json
{
	"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": "https://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:

```bash
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:

```php
$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
<?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:

```json
{
            "type": "vcs",
            "url": "https://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](https://laravel.com/docs/master/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](https://deployer.org/).

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](https://www.digitalocean.com/community/tutorials/how-to-move-an-apache-web-root-to-a-new-location-on-ubuntu-16-04) 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:

```php
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:

```php
@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.

```php
@setup

	// the repository to clone
    $repo = 'git@bitbucket.org: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:

```php
@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:

```php
@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:

```php
@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:

```php
@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:

```php
@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:

```php
@story('deploy-staging', ['on' => 'staging'])
    dir
    git
    install
    live
    deployment_cleanup
@endstory
```
```php
@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:

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

@story('update')
update-plugin
@endstory
```
Here we are using [WP-CLI](https://wp-cli.org/) to perform the update ( but you can also do it via the admin panel if you prefer )

You can execute it by typing:
```php
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](https://roots.io/bedrock/).

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

It looks like this:

```php
├── 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](https://github.com/vlucas/phpdotenv).

 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 [https://time2hack.com](http://time2hack.com/composer-wordpress-deployment/) on April 27, 2020.