Here's Deployer recipes to deploy Bedrock


#1

I recently had to deploy Bedrock using Deployer. Therefore I wrote some recipes to handle deployment, push/pull of database and files, .env file generation, Sage assets, Trellis an more.

Maybe these are useful to someone else as well: https://github.com/FlorianMoser/bedrock-deployer

I even managed to deploy to a shared hosting (Plesk, chroot) by applying some modifications to the common Deployer recipes: https://github.com/FlorianMoser/plesk-deployer

Enjoy!


#2

I don’t suppose you intend on having a version of this which doesn’t force users to be using Trellis? Currently its more of a Bedrock-Trellis recipe than just bedrock :smiley:


#3

There is only one (optional) recipe for Trellis: trellis:remove. And all it does is remove Trellis form the deploy path. This is useful if you have Trellis checked into your repo, but don’t use it for deployment.

The pull:db and push:db recipes currently require a running Vagrant server. But not necessarily Trellis.

But maybe I should try to rewrite these recipe to use normal SSH.


#4

I have Deployer recipes for virgin Bedrock, if you’re interested. Usable with Valet, for example.

See my fork of the unmaintained deployer-wp-recipes. I can provide a sample deploy deployer.php and servers.yml if you’re interested.


#5

@danielroe A sample deploy files would be awesome! I’m new to the Deployer world, but would rather go that route then capistrano again! Thanks


#6

Here are a few examples:

Sample deploy.php
<?php

 // composer require --dev cstaelen/deployer-wp-recipes
 // composer require vlucas/phpdotenv

namespace Deployer;
require __DIR__ . '/vendor/autoload.php';

require 'recipe/common.php';
require 'vendor/cstaelen/deployer-wp-recipes/recipes/assets.php';
require 'vendor/cstaelen/deployer-wp-recipes/recipes/cleanup.php';
require 'vendor/cstaelen/deployer-wp-recipes/recipes/db.php';
require 'vendor/cstaelen/deployer-wp-recipes/recipes/uploads.php';
require 'vendor/vlucas/phpdotenv/src/Dotenv.php';

/**
 * Server Configuration
 */

// Define servers
inventory('servers.yml');

// Default server
set('default_stage', 'production');

// Temporary directory path
set('tmp_path', '/tmp/deployer');

set('wp-recipes', [
  'theme_name'        => 'my-theme',
  'theme_dir'         => 'web/app/themes/',
  'shared_dir'        => '{{deploy_path}}/shared',
  'assets_dist'       => 'web/app/themes/my-theme/',
  'local_wp_url'      => '',
  'remote_wp_url'     => '',
  'clean_after_deploy'=>  [
    'deploy.php',
    '.gitignore',
    '*.md'
  ]
]);

/**
 * Bedrock Configuration
 */

// Bedrock project repository
set('repository', 'git@github.com:me/my-theme.git');

// Bedrock shared files
set('shared_files', ['.env', 'web/.htaccess']);

// Bedrock shared directories
set('shared_dirs', ['web/app/uploads', 'web/app/w3tc-config']);

// Bedrock writable directories
set('writable_dirs', ['web/app/uploads']);

task('setup:vars', function () {
  set('http_user', $user);
  set('http_group', $user);
  set('tmp_path', '/tmp/deployer-' . get('theme_name'));
})->desc('Setup variables');

/**
 * Backup all shared files and directories
 */
task('setup:backup', function () {
    $currentPath = '{{deploy_path}}/current';
    $tmpPath = get('tmp_path');

    // Delete tmp dir if it exists.
    run("if [ -d $tmpPath ]; then rm -R $tmpPath; fi");

    // Create tmp dir.
    run("mkdir -p $tmpPath");

    foreach (get('shared_dirs') as $dir) {
        // Check if the shared dir exists.
        if (test("[ -d $(echo $currentPath/$dir) ]")) {
            // Create tmp shared dir.
            run("mkdir -p $tmpPath/$dir");

            // Copy shared dir to tmp shared dir.
            run("cp -rv $currentPath/$dir $tmpPath/" . dirname($dir));
        }
    }

    foreach (get('shared_files') as $file) {
        // If shared file exists, copy it to tmp dir.
        run("if [ -f $(echo $currentPath/$file) ]; then cp $currentPath/$file $tmpPath/$file; fi");
    }
})->desc('Backup all shared files and directories');


/**
 * Purge all files from the deploy path directory
 */
task('setup:purge', function () {
    // Delete everything in deploy dir.
    run('rm -R {{deploy_path}}/*');
})->desc('Purge all files from the deploy path directory');


/**
 * Restore backup of shared files and directories
 */
task('setup:restore', function() {
    $sharedPath = "{{deploy_path}}/shared";
    $tmpPath = get('tmp_path');

    foreach (get('shared_dirs') as $dir) {
        // Create shared dir if it does not exist.
        if (!test("[ -d $sharedPath/$dir ]")) {
            // Create shared dir if it does not exist.
            run("mkdir -p $sharedPath/$dir");
        }

        // If tmp shared dir exists, copy it to shared dir.
        run("if [ -d $(echo $tmpPath/$dir) ]; then cp -rv $tmpPath/$dir $sharedPath/" . dirname($dir) . "; fi");
    }

    foreach (get('shared_files') as $file) {
        // If tmp shared file exists, copy it to shared dir.
        run("if [ -f $(echo $tmpPath/$file) ]; then cp $tmpPath/$file $sharedPath/$file; fi");
    }
})->desc('Restore backup of shared files and directories');


/**
 * Configure known_hosts for git repository
 */
task('setup:known_hosts', function () {
    $repository = get('repository');
    $host = '';

    if (filter_var($repository, FILTER_VALIDATE_URL) !== FALSE) {
        $host = parse_url($repository, PHP_URL_HOST);
    // } elseif (preg_match('/^git@(?P<host>\w+?\.\w+?):/i', $repository, $matches)) {
    } elseif (preg_match('/^git@(.*):/i', $repository, $matches)) {
        $host = $matches[1];
    }

    if (empty($host)) {
        throw new \RuntimeException('Couldn\'t parse host from repository.');
    }

    run("ssh-keyscan -H -T 10 $host >> ~/.ssh/known_hosts");
})->desc('Configure known_hosts for git repository');

/**
 * Setup success message
 */
task('setup:success', function () {
    Deployer::setDefault('terminate_message', '<info>Successfully setup!</info>');
})->once()->setPrivate();


/**
 * Reload php-fpm service
 */
task('php-fpm:reload', function () {
   run('sudo /etc/init.d/php7.0-fpm reload');
})->desc('Reload php-fpm service');


/**
 * Reload varnish service
 */
task('varnish:reload', function () {
   run('sudo /etc/init.d/varnish reload');
})->desc('Reload varnish service');


/**
 * Deploy task
 */
task('deploy', [
    'deploy:prepare',
    'deploy:lock',
    'deploy:release',
    'deploy:update_code',
    'deploy:shared',
    // 'sage:install',
    'deploy:vendors',
    'deploy:assets',
    'deploy:writable',
    'deploy:symlink',
    'deploy:unlock',
    'cleanup',
    'varnish:reload',
    'php-fpm:reload',
])->desc('Deploy your Bedrock project');
after('deploy', 'success');


/**
 * Setup task
 */
task('setup', [
    'setup:backup',
    'setup:purge',
    'deploy:prepare',
    'setup:restore',
    'setup:known_hosts',
])->desc('Setup your Bedrock project');
after('setup', 'setup:success');

before('db:cmd:pull', 'env:uri');
before('db:cmd:push', 'env:uri');

after('deploy', 'deploy:cleanup');

before('deploy', 'setup:vars');
before('setup', 'setup:vars');
before('deploy:unlock', 'setup:vars');
before('uploads:push', 'setup:vars');
before('db:cmd:push', 'setup:vars');
before('db:cmd:pull', 'setup:vars');
Sample servers.yml
# servers.yml

.base: &base
  branch: master
  theme_name: my-theme
  deploy_path: '/var/www/{{ hostname }}'

my-client.staging.example.com:
  <<: *base
  user: staging
  stage: staging
  deploy_path: '/home/staging/web/{{ hostname }}'

my-client.org.uk:
  <<: *base
  user: my-theme
  stage: production
  deploy_path: '/home/my-client/web/{{ hostname }}'

As I mentioned, these are heavily based on the samples from the original deployer-wp-recipes and rely on my updated fork.

You can use my fork by running:

composer config repositories.deployer-wp-recipes vcs https://github.com/danielroe/deployer-wp-recipes && composer require --dev cstaelen/deployer-wp-recipes=dev-master


#7

Awesome, thanks! This is very help to see as well. Learning how to use a fork in composer is helpful too!