JaggyGauran

Freelance developer, and designer

My Envoy Deployment Script

I am a huge fan of Laravel Forge and Envoyer for my server provisioning and deployment needs. Check them out if you haven’t already, they save me a lot of time whenever I’m handling large scale projects.

There are cases though that we all end up writing our own build and deployment scripts, or for some, even doing it manually.

Setup

Server Access

First of all, you have to setup your SSH keys with your server. We’ll use 104.131.160.181 and envoy as our deployment user.

ssh-copy-id deploy@104.131.160.181

Now, connect to the shell and we’ll disable sudo for restarting php-fpm:

# Connect to the server
ssh deploy@104.131.160.181

# Disable sudo for the user `envoy`
echo "envoy ALL=NOPASSWD: /usr/sbin/service php7.0-fpm restart" | sudo tee -a /etc/sudoers.d/php-fpm

Once we've got that disabled, we can now go to our deployment script.

Directory Structure

This is the directory structure for my setup:

| /home/envoy/
|- jag.gy
|-- .env              # laravel dotenv file
|--- current/         # sym linked to the latest release
|--- releases/
|---- 20150101230918/ # timestamp of the release

Take note of the .env file under the website domain.

Environment File

In the .env file, I added a SERVER_PRODUCTION variable for the envoy script so we don't have to commit any sensitive data in version control.

APP_ENV=local
APP_DEBUG=true
APP_KEY=SomeRandomString

DB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync

MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

SERVER_PRODUCTION=homestead@localhost

Envoy Script

@setup
    function env(string $key) {
        $dotenv = file_get_contents('.env');
        $rows   = explode("\n", $dotenv);

        $search = array_filter($rows, function ($row) use ($key) {
            if (strstr($row, $key)) {
                return $row;
            }
        });

        $variable = reset($search);
        $segments = explode('=', $variable);
        $user = end($segments);

        return $user;
    }

    $now      = date('YmdHis');
    $home     = '/home/forge';
    $domain   = 'default';
    $base     = "{$home}/{$domain}";
    $releases = "{$base}/releases";
    $current  = "{$releases}/{$now}";

    $repository        = 'https://github.com/jaggy/blog/archive/master.zip';
    $production_server = env('SERVER_PRODUCTION');
@endsetup

@servers(['production' => $production_server])

@macro('deploy')
    download_latest_release
    install_composer_dependencies
    optimize_release
    activate_current_release
    setup_environment_variables
    reload_phpfpm_server
@endmacro

@task('download_latest_release')
    cd {{ $releases }}

    wget {{ $repository }}
    unzip master.zip
    mv blog-master {{ $now }}
    rm master.zip
@endtask

@task('install_composer_dependencies')
    cd {{ $current }}

    composer install --prefer-dist --no-dev
@endtask

@task('optimize_release')
    cd {{ $current }}

    php artisan clear-compiled

    php artisan optimize
    php artisan route:cache
    {{-- php artisan config:cache --}}
@endtask

@task('activate_current_release')
    cd {{ $domain }}

    {{-- Delete symbolic link --}}
    rm current

    {{-- Link the latest release to the current release --}}
    ln -s {{ $current }} current
@endtask

@task('setup_environment_variables')
    cd {{ $current }}

    {{-- Link the global .env file to the current release --}}
    ln -s {{ $base }}/.env .
@endtask

@task('reload_phpfpm_server')
    sudo /usr/sbin/service php7.0-fpm reload
@endtask

To run the script:

envoy run deploy

Just to give a rundown on what the script does:

  1. Download the master archive file from Github and stores it at the releases folder.
  2. Install the composer dependencies.
  3. Cache the routes and compile the laravel classes.
  4. Switch the symbolic link to the new release.
  5. Create a symbolic link of the .env in the latest release
  6. Reload the PHP-FPM server.

Note

I disabled the php artisan config:cache since for some reason, whenever I execute that script, the application isn't reading the .env and starts throwing a cipher key error.

I want to expand the script where we store the github repository in the environment file as well. Also, I wanna add a branch or even a commit sha flag for rolling back releases as well.

I also wanna add a release folder clean up to remove the bloat on the releases folder and move the storage/ folder under the domain for the cache to not be affected by any of the releases.

Thoughts

Here are some additional or different things you can do for your deployment.

Pulling the changes from Github

Rather than downloading an archive file, you can just go to your project directory and executing a git pull origin master from there.

cd {{ $current }}

git pull origin {{ $branch }}

Comiling your Assets

Some people don't want to commit their compiled stylesheets and javascript files, which is reasonable. (I like committing them since I don't like the idea of production compiling any of the assets, unless I have a build server that does the job though).

cd {{ $current }}

gulp stylus --production     # or whatever pre-processor you fancy.
gulp browserify --production
gulp optimize-images         # script to run image optimizations in resources/assets/images/

Resources

Most of the things I applied here I learned from using Envoy and Forge and from subscribing to Servers for Hackers by @fideloper