Skip to content
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This package contains [Deployer](https://deployer.org/) recipes used to help dep

## Recipes

* [WordPress](recipes/wordpress.md)
* [Static site](recipes/static.md) -
* [Slack](recipes/slack.md) - send a notification to Slack when a deployment is complete

Expand Down
2 changes: 2 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ For deploying WordPress websites.
cp vendor/studio24/deployer-recipes/examples/wordpress.php ./deploy.php
```

See [WordPress docs](recipes/wordpress.md).

[Source](../recipe/wordpress.php)

### Laravel:
Expand Down
172 changes: 172 additions & 0 deletions docs/recipes/wordpress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# WordPress

Deploy a WordPress site.

[Source](../../recipe/wordpress.php)

## Background

We manage our WordPress sites using the following directory structure:

```
├── docs
└── web
| ├── content
| | ├── cache
| | ├── mu-plugins
| | ├── plugins
| | ├── themes
| | └── uploads
| ├── wordpress
| └── wp-config.php
├── composer.json
├── .env
└── README.md
```

WordPress is not in source control and is installed to `web/wordpress`. This folder is set to be writable to support auto-updates to WordPress core.

Source controlled plugins and themes are in the `web/content` folder.

Configuration is stored in `web/wp-config.php`

Sensitive or environment configuration values are stored in a `.env` file in the project root.

Documentation is stored in the `docs` folder.

## Installation

### Requirements

This recipe requires [WP CLI](https://wp-cli.org/) to exist on the remote server.

### Recipe

You can copy an example deployment file:

```
cp vendor/studio24/deployer-recipes/examples/wordpress.php ./deploy.php
```

### Loading environment variables in wp-config

Local environment variables set in `.env` can be loaded in your wp-config file.

First install [vlucas/phpdotenv](https://packagist.org/packages/vlucas/phpdotenv) and include the Composer autoloader.

Set your local environment variables in `.env` via name and value pairs:

```
DB_NAME="wp_database"
S3_BUCKET="devbucket"
```

This can be loaded in your wp-config file via:

```php
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
```

You can then reference the environment values in wp-config via `$_ENV`. E.g.

```php
define('DB_NAME', $_ENV['DB_NAME']);
```

You can also refer to other environment variables in your `.env` file via `${NAME}`. E.g.

```
BASE_DIR="/var/webroot/project-root"
CACHE_DIR="${BASE_DIR}/cache"
```


## Tasks

The static site recipe has 2 new tasks it runs:

### WordPress

WordPress core is not in source control and should have auto-update enabled on the server.

You can change the WordPress path via:

```
set('wordpress_path', 'web/wordpress');
```

If you change it from the default, you will need to add this path to `shared_dirs` and `writable_dirs`.

#### WordPress update strategy

To let WordPress run updates automatically the `WP_AUTO_UPDATE_CORE` setting must exist in your config:

```
// Minor updates are enabled, development, and major updates are disabled (recommended for production)
define('WP_AUTO_UPDATE_CORE', 'minor');

// Development, minor, and major updates are all enabled
define('WP_AUTO_UPDATE_CORE', true);
```

It is also possible to configure this via [filters](https://developer.wordpress.org/advanced-administration/upgrade/upgrading/#configuration-via-filters).

#### On deployment

On deployment, if WordPress does not exist in this path, it installs it. If WordPress does exist, no core updates are made on deployment.

#### Manually running WordPress updates

If you need to manually update WordPress run:

```bash
dep wp core update staging
```

You can update to a specific version via:

```bash
dep wp core update --version=<version> staging
```

### Composer

If `composer.json` exists in the project root, the `deploy:vendors` task is automatically run.

You can check this by running `dep tree deploy` which will show you the tasks to be run.

#### Multiple Composer files in a WordPress project

It is not recommended to have more than one composer.json file in a project. If you do have one, you should move the
dependencies into the root composer file.

## WP CLI
If you need to call any [WP CLI](https://wp-cli.org/) commands this recipe includes the `wp()` function to allow you to do this.

This requires WP CLI to exist on the server.

Usage:

```php
wp('command');
```

E.g. to run `wp core version`

```php
wp('core version');
```

This automatically sets the current environment using the `stage` variable. You can pass this manually as the second param:

```php
wp('core version', 'staging');
```

## Configuration

### Required configuration

* `wordpress_path`: directory to install WordPress to relative to the release_path

76 changes: 69 additions & 7 deletions recipe/wordpress.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,29 @@

namespace Deployer;

require_once 'recipe/wordpress.php';
require_once 'recipe/common.php';
require_once __DIR__ . '/common.php';

// Path to WordPress core
set('wordpress_path', 'web/wordpress');

// Shared files that need to persist between deployments
set('shared_files', [
'config/wp-config.local.php'
'.env'
]);

// Shared directories that need to persist between deployments
set('shared_dirs', [
'.well-known',
'web/wp-content/uploads',
'web/wp-content/cache',
'web/content/uploads',
'web/content/cache',
]);

// Writable directories
set('writable_dirs', [
'web/wp-content/uploads',
'web/wp-content/cache'
'web/content/uploads',
'web/content/cache',
'{{wordpress_path}}',
]);

// Deployment and HTTP users
Expand All @@ -33,6 +37,64 @@
// Array of remote => local file locations to sync to your local dev environment
set('sync', [
'images' => [
'shared/web/wp-content/uploads/' => 'web/wp-content/uploads'
'shared/web/content/uploads/' => 'web/content/uploads'
],
]);

// Install WordPress
task('deploy:wordpress_install', function() {
$wordPressPath = get('wordpress_path', false);

cd('{{release_path}}');
run(sprintf('mkdir -p %s', $wordPressPath));
$stage = get('stage');

// Is WP already installed?
if (test(sprintf('[ -f %s/wp-blog-header.php ]', $wordPressPath))) {
writeln('Skipping WordPress download, current installed version: ');
} else {
// Install WP
// @see https://developer.wordpress.org/cli/commands/core/download/
wp(sprintf('core download --skip-content --path=%s', $wordPressPath), $stage);
writeln('Downloaded WordPress version: ');
}
wp(sprintf('core version --path=%s', $wordPressPath), $stage);
});

// Optionally install Composer vendors if composer.json exists in project root
task('deploy:vendors_if_exists', function() {
if (test('[ -f {{release_path}}/composer.json ]')) {
writeln('Install vendors, composer.json found');
invoke('deploy:vendors');
} else {
writeln('Skipping, no composer.json found');
}
});

/**
* Run WP CLI
*
* @param string $command wp command, e.g. core download
* @param ?string $stage environment, e.g. production. Defaults to the current stage name
* @return void
* @throws Exception\Exception
* @throws Exception\RunException
* @throws Exception\TimeoutException
*/
function wp(string $command, ?string $stage = null)
{
if (null === $stage) {
$stage = get('stage', 'production');
}
run(sprintf('WP_ENV=%s wp %s', $stage, $command), real_time_output: true);
}


// Deployment tasks
desc('Deploys your project');
task('deploy', [
'deploy:prepare',
'deploy:vendors_if_exists',
'deploy:wordpress_install',
'deploy:publish',
]);