Zero-Downtime Deployments for Laravel Apps with deployer

• 2 min read

Matt Stauffer recently tweeted about deployment in the Laravel world and how people ask him questions about it. In this post I wanna share how I usually deploy my Laravel apps.

My deployment workflow is dictated by a small set of questions I have to ask myself first:

  • Is this a little side project that only I use or is it an app which is exposed to hundreds/thousands of users and will generate money?
  • How complex is the deployment process itself? - Is a git pull sufficient? - Do I have to run database migrations? - Do I have to install composer dependencies?

If the answers are "just a side project" and "just install composer dependencies" is usually stick to the "Deploy script" feature of Laravel Forge. If the answers are more like "it's a bigger project" and "there are more steps to deploy this app" I use deployer.org (You can deploy any kind of app with it, not just Laravel or PHP projects).

One of the key benefits of using deployer is, that it comes with support for "zero downtime" deployments out of the box. This means, that each deployment is created in its separate folder and only when all your deployment steps go through without errors, the new release gets activated by placing a symlink in your webroot to the new release.

When you first setup deployer you have the option to select one of many pre-made recipes for popular Frameworks like Laravel, Symfony or Drupal. The Laravel recipe looks like this. It will automatically clear your caches and config values for you. Nice 👌.

At work, we use a customized "Common" recipe. Our deploy task looks like this:

desc('Deploy your project');
task('deploy', [
    'deploy:prepare',
    'deploy:lock', # Lock the server for further deploys
    'deploy:release', # Create new release
    'deploy:update_code', # Fresh Git clone
    'deploy:shared', # Setup shared dirs and files
    'deploy:writable', # Fix permissions
    'deploy:vendors', # Run composer install
    'deploy:clear_paths',
    'artisan:cache:clear',
    'artisan:view:clear',
    'artisan:config:cach'
    'deploy:symlink', # Create the symlink
    'deploy:restart-php-fpm',
    'deploy:bugsnag-deploy-tracking', # Send API request to Bugsnag
    'deploy:unlock',
    'cleanup', # Cleanup old releases
    'success',
]);

So it's not just a git pull.

The switch to deployer from our previous solution is now over 12 months ago and I can say, that it saved our ass multiple times.

So my summary of deployer:

  • Comes with Zero Downtime support out of the box.
  • Can work on shared hosting (depends on the hosting provider, but it's possible).
  • Flexible and extendable with your own tasks.
  • Deployment are usually triggered from your machine and not through a Web UI or through ChatOps. (Can be done, but needs a bit of setup)

I can highly recommend the following articles, if you want to read more about deployment:

  • https://zachholman.com/posts/deploying-software
  • http://lorisleiva.com/laravel-deployment-using-gitlab-pipelines/