Using Composer for easy WordPress Deployments
Learn how you can use Composer with WordPress to easily deploy it and manage it on multiple servers. Install & manage your WordPress themes and Plugins via Composer.
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 can install WordPress core, themes, plugins, etc., and update and delete them when needed via Composer.
To achieve this, we will use the following tools:
- Composer - for managing packages
- WPackagist - the repository for WordPress plugins and themes
- Laravel 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 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":"https://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": "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:
composer install --prefer-dist
Composer will now install WordPress within the wordpress
directory in the root of our project.
To be able to manage WordPress with Composer fully, 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' );
We will also create a .htaccess file in the root directory with the following contents:
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
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 that we will add them to the .gitignore
file as well.
Now we end up with a very simple project structure:
/
βββ .htaccess
βββ composer.json
βββ composer.lock
βββ wp-config.php
βββ wp-content/
βββ wordpress/
βββ vendor/
βββ .gitignore
Note: Make sure your Site address (URL) is yourwebsite.com
, and your WordPress address (URL) are: yourwebsite.com/wordpress
. This is basically like giving WordPress its directory with the second method.
Using composer to install WordPress plugins and themes from private repositories
You may want to install plugins or themes hosted in private repositories on Github, Bitbucket or elsewhere, but not on WPackagist.
That is possible, but there are two things you need to do:
- provide your credentials in an
auth.json
file - within the
composer.json
, in therepositories
section, you need to tell Composer where to look for the repository:
{
"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 with Composer let's see how to deploy it.
As mentioned previously, we will use Laravel Envoy to write a deployment script for WordPress.
The reason why we are using Envoy is because of its simplicity. You can also decide to use a different tool like Deployer.
Let's go ahead and download Envoy.
We can deploy our WordPress application to different servers: development, staging, and production with the deployment script.
Because the project structure is so lightweight, it will be easy to write the deployment steps.
Here is what our deployment script for WordPress will look do:
- Create a new release with a timestamp in a
releases
directory on your server - Clone the repository
- Install all the dependencies
- Copy
wp-config
,.htaccess
and other files specific to the server environment (e.g. production) - Create a symlink of the
uploads
directory to the new release - Create a symlink of the new release to the domain's document root directory on the server
- 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 unfamiliar with 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
- a 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 must be executed on the specified server.
Envoy works because 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 must 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 release directory structure, as discussed above.
@setup
// the repository to clone
$repo = '[email protected]: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 actually to 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 ensure 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 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 entirely via Composer.
Keeping WordPress in sync with Composer
As you already know, updating plugins via the WordPress Admin panel is possible. 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 correctly.
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 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 shown how to manage WordPress with Composer and deploy it with your 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 a predefined 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.
You can write a deployment script with Envoy, Deployer, or another 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 how to easily deploy WordPress to multiple environments when used with Composer.
First, we defined a composer.json
file, which enabled us to install the WordPress Core and 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 in modern development workflows. It ensures relatively 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.
This post is sponsored by PluralSight