How to set up your Laravel application for zero-downtime (Envoyer/Capistrano) deploys
Posted on March 29, 2017 | By Matt Stauffer
Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.
If you've ever worked with Capistrano or Envoyer, you've probably seen a directory structure in your webroot that looks something like this:
root root 4096 Mar 29 18:44 . root root 4096 Mar 28 14:15 .. root root 47 Mar 29 14:54 current -> ./releases/1490824249 root root 4096 Mar 29 14:50 releases
Where you're expecting to see your webroot containing your Git repository, instead it's this weird structure. What gives?
A brief introduction to Capistrano-style zero-downtime deploys
The reason you're getting zero-downtime deploy from these tools is because the entire deploy process—clone, composer install, etc.—doesn't happen in the directory that is currently serving your site. Instead, each new release gets its own separate "release" directory, all while your site is still being served from its current "release" directory.
- current -> ./releases/1490802721 * apache/nginx serves from this directory - releases - 1490802133 (the new release you're building right now) - 1490802721 (latest complete release) - 1490803081 (a little bit older release) - 1490824249 (an even older release)
All of these release directories are just subdirectories of
releases. Each directory here represents one of your deploys, and each directory individually has everything needed to serve your site. Your web server points to
yourproject/current/public and therefore the "currently served" release is just that which has a symlink pointed at it from
So, once the build process is complete for each new release, your deploy tool will delete the
current symlink and create a new
current symlink that points to your latest release. Boom. Now that release is live.
In general, Laravel is no different from any other project in that this style of deployment works great. In fact, a tool provided by Taylor Otwell, Envoyer, is predicated around this release style.
However, every tool has a different set of caveats around how to handle them well in zero-downtime settings. Here's why:
There are always some things that you want to persist between releases. Most of it lives in databases and caches, which is fine—nothing's wiping your database on every deploy. But some isn't. Take the
storage folder; do you want to wipe that every time you push a new release? Naw. What about the
.env file? Definitely naw. So there are a few quick tricks.
How to set up your deploy for Laravel
Remember: If you use Envoyer, this is all handled for you. But if you don't, here's what to do.
- Clone your release into a new release folder. This should be handled by your deploy tool.
composer install -o --no-interaction
php artisan migrate --no-interaction --force
- (optional, if you don't commit your built scripts)
yarn) and either
gulp --production(Elixir) or
npm run production(Mix)
rm -rf storage && ln -s ../../storage ./
storagedirectory and symlink it to a
storagefolder in the parent)
ln -s ../../.env ./
.envfile to a
.envfile in the parent)
php artisan route:cache
php artisan config:cache
- Update the
currentsymlink to your new release. This should be handled by your deploy tool.
php artisan queue:restart
(Restart the queue.)
This is all you need to do on every deploy. As you can tell, you'll end up with a root directory that looks a bit more like this:
root root 4096 Mar 29 18:44 . root root 4096 Mar 28 14:15 .. root root 1033 Mar 29 18:44 .env root root 47 Mar 29 14:54 current -> ./releases/1490824249 root root 4096 Mar 29 14:50 releases root root 4096 Mar 29 14:51 storage
And, of course, you'll need to create the
.env file and the
storage directory and subdirectories before you run your build script for the first time.
But that's it! You're now ready to go! Get deploying!
Looking for a list of steps to take on every deploy, regardless of whether or not it's Capistrano?
composer install -o --no-interactions php artisan migrate --no-interaction --force yarn && npm run production php artisan route:cache php artisan config:cache php artisan queue:restart
Comments? I'm @stauffermatt on Twitter
Tags: laravel • envoyer • capistrano • deploy