March 05, 2015

Enhancing Envoy Deployment

I take feedback from your comments and my own recent experience to develop a more advanced Envoy deployment script.

We'll grab from GitHub quicker, use ACL permissions, see how to allow our deploy user to use sudo securely, persist log files and more edge cases.

The improved script will handle environment files, the log directory, allowing the deploy user to reload PHP-FPM and more!Here are all the tweaks we make to our Envoy configuration.

The Original

Here's what we're starting from:

@servers(['web' => 'deploy-ex'])

<?php
$repo = 'git@github.com:Servers-for-Hackers/deploy-ex.git';
$release_dir = '/var/www/releases';
$app_dir = '/var/www/app';
$release = 'release_' . date('YmdHis');
?>

@macro('deploy', ['on' => 'web'])
    fetch_repo
    run_composer
    update_permissions
    update_symlinks
@endmacro

@task('fetch_repo')
    [ -d {{ $release_dir }} ] || mkdir {{ $release_dir }};
    cd {{ $release_dir }};
    git clone {{ $repo }} {{ $release }};
@endtask

@task('run_composer')
    cd {{ $release_dir }}/{{ $release }};
    composer install --prefer-dist;
@endtask

@task('update_permissions')
    cd {{ $release_dir }};
    chgrp -R www-data {{ $release }};
    chmod -R ug+rwx {{ $release }};
@endtask

@task('update_symlinks')
    ln -nfs {{ $release_dir }}/{{ $release }} {{ $app_dir }};
    chgrp -h www-data {{ $app_dir }};
@endtask

And now we'll go over the changes in the video.

Always clone into the master branch

Change

git clone {{ $repo }} {{ $release }};

to:

git clone -b master {{ $repo }} {{ $release }};

ACL's over group permissions

In the production server:

Check setfacl exists:

which setfacl

# If doesn't exist:
sudo apt-get install -y acl

Then use it:

# Inspect current ACL's
getfacl /var/www

# Set current and default ACL's for /var/www
sudo setfacl -Rm g:www-data:rwx,d:g:www-data:rwx /var/www

Now user deployer (which is part of group www-data) can read, write and execute directories in files in /var/www despite current group settings and permissions.

Visudo

Allow group www-data to run sudo service php5-fpm [reload|restart] without requiring a password:

# Run as super user
sudo visudo

Add:

%www-data ALL=(ALL:ALL) NOPASSWD:/usr/sbin/service php5-fpm restart,/usr/sbin/service php5-fpm reload

Or we can create and edit file /etc/sudoers.d/wwwdata and add that same line.

Then lastly have the deploy user reload PHP-FPM after deployment. Add the following to the bottom of the update_symlinks section:

sudo service php5-fpm reload

Handle .env File:

We don't want our .env file in our code repository. We'll put it on our server and symlink to it, so it's available to each deployed version of our application.

On the production server:

# Create and edit the .env file as needed
vim /var/www/.env

Then in the Envoy configuration, update the update_symlinks section:

cd {{ $release_dir }}/{{ $release }};
ln -nfs ../../.env .env;
chgrp -h www-data .env;

Handle Composer with correct environment

Make sure the post-install commands, as defined in Laravel's composer.json, run in the correct environment. Add the following into the run_composer section:

composer install --prefer-dist --no-scripts;
php artisan clear-compiled --env=production;
php artisan optimize --env=production;

Persist Log Files

The directory storage/logs will get overwritten every time we deploy. We want our logs to be in a central place, however. We can use symlinks to accomplish this.

On the production server:

sudo mkdir /var/www/logs
sudo chown www-data:www-data /var/www/logs

Then in the Envoy configuration, under update_symlinks, add the following:

rm -r {{ $release_dir }}/{{ $release }}/storage/logs;
cd {{ $release_dir }}/{{ $release }}/storage;
ln -nfs ../../../logs logs;
chgrp -h www-data logs;

Final Product:

@servers(['web' => 'deploy-ex'])

<?php
$repo = 'git@github.com:Servers-for-Hackers/deploy-ex.git';
$release_dir = '/var/www/releases';
$app_dir = '/var/www/app';
$release = 'release_' . date('YmdHis');
?>

@macro('deploy', ['on' => 'web'])
    fetch_repo
    run_composer
    update_permissions
    update_symlinks
@endmacro

@task('fetch_repo')
    [ -d {{ $release_dir }} ] || mkdir {{ $release_dir }};
    cd {{ $release_dir }};
    git clone -b master {{ $repo }} {{ $release }};
@endtask

@task('run_composer')
    cd {{ $release_dir }}/{{ $release }};
    composer install --prefer-dist --no-scripts;
    php artisan clear-compiled --env=production;
    php artisan optimize --env=production;
@endtask

@task('update_permissions')
    cd {{ $release_dir }};
    chgrp -R www-data {{ $release }};
    chmod -R ug+rwx {{ $release }};
@endtask

@task('update_symlinks')
    ln -nfs {{ $release_dir }}/{{ $release }} {{ $app_dir }};
    chgrp -h www-data {{ $app_dir }};

    cd {{ $release_dir }}/{{ $release }};
    ln -nfs ../../.env .env;
    chgrp -h www-data .env;

    rm -r {{ $release_dir }}/{{ $release }}/storage/logs;
    cd {{ $release_dir }}/{{ $release }}/storage;
    ln -nfs ../../../logs logs;
    chgrp -h www-data logs;

    sudo service php5-fpm reload;
@endtask

Resources

All Topics