Zero Downtime Deployments for Laravel Forge

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.

  1. You need to scour your .gitignore file to make sure that you copy any assets needed that AREN'T built on the server, and aren't committed.
  2. 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?

  1. Create a new directory to pull your code into.
  2. Pull that code in.
  3. Pull in any needed items from the current build.
  4. Run composer
  5. Run artisan and npm commands
  6. Backup the current build.
  7. Deploy the new build.
  8. 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 as my default domain.

# delete our old deploy directory if it exists
if [ -d "/home/forge/deploy" ] 
	rm -rf /home/forge/deploy

# 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 .

# copy needed files that aren't committed to git
cp /home/forge/ /home/forge/deploy/.env
cp -r /home/forge/ /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" ] 
	rm -rf /home/forge/backup

# backup the current build
mv /home/forge/example /home/forge/backup

# deploy the new build
mv /home/forge/deploy /home/forge/

# move into new build directory
cd /home/forge/

# 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.

Happy coding.

By Vince Kronlein