Zero Downtime Deployments with Laravel Forge (free edition)

This tutorial demonstrates how anyone can implement zero-downtime deployments when using Forge without using Laravel Envoyer. To do that we will use Laravel Envoy, a tool that helps setup tasks during deployment using Blade syntax.


If you need advanced features (like multi-server support), a beautiful UI, or a hassle-free deployment experience, I highly recommend trying out Laravel Envoyer. Envoyer offers a wide range of features that can streamline your deployment process and provide a user-friendly interface

To start, you’ll need Envoy installed globally on your production server. To do that simply run composer global require laravel/envoy.

After this is completed, you need to create your Envoy.blade.php file in the root of your project and have it committed into git. As a starting point you may use the following file, which should cover the most common deployments in a typical laravel project.

@servers(['production' => ''])

$repository = '[email protected]:yourorganization/yourprojectname.git';
$releasesPath = '/home/forge/yoursitename/releases';
$siteRootDirectory = '/home/forge/yoursitename';
$release = date('YmdHis');
$newReleaseDirectory = $releasesPath .'/'. $release;


echo 'Cloning repository'
git clone --depth 1 {{ $repository }} {{ $newReleaseDirectory }}

echo 'Enter New Release directory'
cd {{ $newReleaseDirectory }}

@if ($commit)
echo 'Switching to specific commit with git reset --hard {{ $commit }}'
git reset --hard {{ $commit }}

echo 'Install composer dependencies'
composer install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist --no-dev --optimize-autoloader

php artisan optimize:clear

echo 'Linking .env file'
ln -nfs {{ $siteRootDirectory }}/.env {{ $newReleaseDirectory }}/.env

npm i
npm run prod


@task('activate', ['on' => 'production'])
echo "Linking storage directory"
rm -rf {{ $newReleaseDirectory }}/storage
ln -nfs {{ $siteRootDirectory }}/storage {{ $newReleaseDirectory }}/storage

cd {{ $newReleaseDirectory }}

php artisan optimize
php artisan migrate --force

php artisan storage:link
php artisan nova:publish
php artisan horizon:publish

echo 'Linking current release'
ln -nfs {{ $newReleaseDirectory }} {{ $siteRootDirectory }}/current

php artisan horizon:terminate

( flock -w 10 9 || exit 1
echo 'Restarting FPM...'; sudo -S service php8.2-fpm reload ) 9>/tmp/fpmlock

After that you will need to make a few changes to your Laravel Forge site to use the above file when deploying.

Step 1: Update your Web Directory to /current/public. The current symlink will be updated at the end of each deployment, and will always be pointing to last successful release.

Step 2: Update your Forge deployment script to trigger Envoy.

cd /home/forge/yoursitename/current

$FORGE_PHP /home/forge/.config/composer/vendor/bin/envoy run deploy

Step 3: If your site is already deployed you will need to move to do some manual work to move the files into the structure we need.

Inside your /home/forge/yoursitename directory you should have the following files/directories.

  1. Your laravel’s .env file
  2. Your auth.json (optional – needed only if you are pulling private packages like Nova)
  3. The releases folder where all of your deployments will be stored
  4. Your storage folder (which is symlinked during deployment)

After this, you will need to create the first symlink, since it is needed for the first actual deployment through Forge to happen.

ln -nfs /home/forge/yoursitename/releases/yourreleasetimestamp /home/forge/yoursitename/current

Your are all set! Let me know how it went!

Leave a Reply

Your email address will not be published. Required fields are marked *