Zero Downtime Deployments for Laravel Forge
Keeping your site running smoothly and having completely clean installations for each deployment is a must for my workflow, and this is a cinch with Laravel Envoyer. But, depending on your client's needs and desires ... sometimes this isn't possible.
So I set out to develop a Forge deploy script to achieve virtually the same effect of deploying to a new directory, then renaming that directory to make it accessible to the webserver.
Before we jump in, let's start with a couple disclaimers.
- You need to scour your
.gitignorefile to make sure that you copy any assets needed that AREN'T built on the server, and aren't committed.
- Backup your current deploy script and your current live directory in case it goes sideways and you need to restore.
We're gonna make some assumptions that you're deploying a Laravel app, if not you'll obviously need to consider the commands you'll need to run, but the main idea should still work if you're following the instructions here.
So what are the steps we need to accomplish?
- Create a new directory to pull your code into.
- Pull that code in.
- Pull in any needed items from the current build.
- Run composer
- Run artisan and npm commands
- Backup the current build.
- Deploy the new build.
- Link the storage directory.
You can of course tweak this for your own needs, perhaps you don't run npm on site ... then leave those out etc.
So here is my deploy script using
example.com as my default domain.
# delete our old deploy directory if it exists if [ -d "/home/forge/deploy" ] then rm -rf /home/forge/deploy fi # create a deploy directory mkdir /home/forge/deploy # move into the directory cd /home/forge/deploy # clone your repository branch git clone -b $FORGE_SITE_BRANCH email@example.com:Example/example.git . # copy needed files that aren't committed to git cp /home/forge/example.com/.env /home/forge/deploy/.env cp -r /home/forge/example.com/storage /home/forge/deploy # install dependencies with composer $FORGE_COMPOSER install --no-interaction --prefer-dist --optimize-autoloader # restart PHP-FPM ( flock -w 10 9 || exit 1 echo 'Restarting FPM...'; sudo -S service $FORGE_PHP_FPM reload ) 9>/tmp/fpmlock # run artisan and node commands php artisan migrate --force npm install npm run dev php artisan config:clear php artisan cache:clear php artisan view:clear php artisan queue:restart # now that scripts are compiled remove the node directory rm -rf node_modules # delete our old backup directory if [ -d "/home/forge/backup" ] then rm -rf /home/forge/backup fi # backup the current build mv /home/forge/example /home/forge/backup # deploy the new build mv /home/forge/deploy /home/forge/example.com # move into new build directory cd /home/forge/example.com # link your storage directory php artisan storage:link
And there you have it.
Only milliseconds of downtime because your using
mv to simply rename the directories instead of copying files from one directory to another.
Tested this personally on several sites I run and it works like a charm.