Deploying PHP Applications with Deployer

May 14, 2016 3 mins read

Deploying PHP Applications with Deployer

Deployer is a deployment tool written in PHP to be used by PHP applications. Deployer ships with out-of-the-box support for various popular PHP frameworks, including Symfony. For a recent project of mine I needed to execute database migrations during deployment, and hence a simple rsync deployment was not enough. That’s where Deployer kicks in.

Getting Started

Add Deployer to your project with composer require deployer/deployer and then unleash the beast with php vendor/bin/dep, which starts the deployment pipeline. To configure the deployment pipeline, you’ll need to create a deploy.php file in the root directory of your project along the lines of:

<?php
require 'recipe/symfony.php';

// Define a server for deployment.
server('prod', $deployHost, $deployPort)
    ->user($deployUser)
    ->forwardAgent()
    ->stage('production')
    ->env('deploy_path', $deployPath); // Define the base path to deploy your project to, e.g. /var/www/awesome/app

// Specify the repository from which to download your project's code.
set('repository', '[email protected]:org/app.git');

To deploy the application, you’ll use the dep command and address the respective stage: dep deploy production. Thats it! Deployer creates the following directory structure on the target server:

/var/www/awesome/app
|--releases
|  |--20160508120631
|--shared
|  |--...
|--current -> /var/www/awesome/app/20160508120631

Configure your webserver to serve from the current symlink and you are all set.

Writing Tasks

Deployer allows to easily define tasks which handle the actual deployment logic. Deployer ships pre-configured tasks tailored to specific frameworks and bundled as receipes. Defining a task and hooking it up in the deploment pipeline is straightforward:

task('database:backup', function () {
    // Your tasks code...
})->desc('Relax, the backup is safe!');

before('database:migrations', 'database:backup');

Deployer also ships with useful functions that are ready to use in your tasks. For example, I use brunch to build frontend assets on the CI server and then upload the built assets to the production server during deployment:

task('deploy:upload-brunch-assets', function() {
    $upload = [
        'web/app.css',
        'web/app.js',
    ];
    foreach ($upload as $path) {
        upload($path, "{{release_path}}/{$path}");
    }
})->desc('Deploy assets built with brunch.');
before('deploy:assets', 'deploy:upload-frontend-assets');

The receipe/symfony.php already defines a database migration task, but the task is not enabled by default. With Deployer and the Symfony receipe, adding the migrations is a breeze by adding the following line to deploy.php:

after('deploy:vendors', 'database:migrate');

Automating and Debugging Deployments

I use Deployer from within GitLab CI, so successful builds are automatically deployed to production. Once properly configured, things typically work, but just in case something goes wrong it’s better to prepare. Luckily, Deployer allows to adjust the level of output verbosity. Running Deployer with dep deploy production -vv stores helpful details in the CI log.

The .gitlab-ci.yml is configured as follows:

job1:
    stage: deploy
    only:
        - master
    tags:
        - deploy
    script: |
        # [...] install, build and test
        php vendor/bin/dep deploy production -vv
    cache:
        paths:
            # speed up builds by caching dependencies
            - vendor/
            - node_modules
            - bower_components

Conclusion

Deployer is easy and fun to use. Tailor the provided receipes to your project and enjoy that all deployment-related configuration is stored in a single file. Deployer has a nice and concise syntax for writing tasks and ships a ton of helpful features. Be sure to give it a try!

Comments

👋 I'd love to hear your opinion and experiences. Share your thoughts with a comment below!

comments powered by Disqus