Skip to main content

Setting Up Zero-Downtime Deployment

When deploying a project, it can lead to temporary downtime for the production application. In order to avoid this, we use Laravel Envoyer to set up zero-downtime deployments.

Which Projects Are Elligible?

Before explaining how to set things up, something worth noting is that we shouldn't be setting this up for every single project. The amount of apps that we can use Envoyer for is limited based on how much we pay, so it should only be used for our most important projects.

Do not set up Envoyer for the following types of sites:

  • Small projects, especially those with little traffic
  • Static websites and front-end only applications (i.e. no PHP)
  • Staging environments

Also, the project should be set up on Laravel Forge before being set up on Envoyer.

Create the Project

With that disclaimer out of the way, let's do this thing!

First, you'll need to be logged into Laravel Envoyer with the admin account. On the dashboard page, click "Add Project" and fill in the details of the project's GitHub repository:

envoyer-new-project.png

⚠️ Note

Only team leaders have access to the Laravel Envoyer admin account credentials. If you are a team leader without access, ask one of the Directors to add you to the veganhacktivists.admin team on Keybase. If you are not a team leader, have them set this up for the project.

Next, we'll need to import the site from Forge. Head to your new project's page, open the "Servers" tab, and then click "Import Forge Server":

envoyer-import-forge-server.png

Select the vegan-hacktivists server, select the project from the list of sites, and then click "Import Server."

Once this is done, click the refresh ? icon under "Connection Status" in Envoyer:

envoyer-connection-status.png

⚠️ Note

You may see a warning that PHP-FPM was unable to be restarted:

envoyer-error-php-fpm.png

If you run into this, it's likely because Envoyer set up the project with the wrong PHP version. Check the site on Forge to see what version of PHP the project is using. Then, click on the pencil ✏️ icon in Envoyer to set the correct PHP version for the project. After that, click the refresh ? button again and all should look good!

Copying From Forge

While we've imported the site from Forge, there are still some things we'll need to do manually in order to complete the Envoyer setup.

Disable Quick Deploy

The first thing we'll need to do is disable "Quick Deploy" if it's enabled. Head to your project's page on Forge and make sure it's disabled:

forge-disable-quick-deploy.png

We're disabling this because we'll no longer be deploying using Forge. To get the equivalent functionality for the new deployments, head to your project settings on Envoyer:

envoyer-settings.png

Then, head to the "Source Control" tab and check "Deploy When Code Is Pushed" and save the project settings:

envoyer-push-to-deploy.png

Setting Up Deployment Hooks

Now we have to replicate whatever is in our deploy script! First, take a look at what's currently there on Forge:

forge-deploy-script.png

Keep that tab open and head over to the "Deployment Hooks" tab on your Envoyer project page:

envoyer-deployment-hooks.png

If you are just setting up the project, you will have four immutable hooks (the ones with a gray background). These are managed by Envoyer, but they are likely not enough to cover all of the deployment process, so we'll have to add our own.

Migrating the Deploy Script

In order to start adding hooks, click the "Add Hook" button. More-or-less, you'll want to consult your deploy script on Forge.

Do not write hooks for the following:

  • Pulling the Git repository
  • Installing Composer dependencies
  • Restarting PHP-FPM

Also, make sure that the "Run As" field is the username associated with the project. This can be checked on the sites page on Forge:

forge-site-list-directory-username.png

Below are some examples which should suffice most projects, but of course, refer to the deploy script to be sure. Refer to the Envoyer screenshot above to see the proper order for these hooks.

Note that {{release}} is a template variable for deployment hooks which refers to the directory of the latest release.

Build front-end assets
cd {{release}}

yarn && yarn prod
Optimize
cd {{release}}

php artisan optimize
Run Database Migrations
cd {{release}}

php artisan migrate --force
Restart Queue

This is only necessary if you've set up a queue for asynchronous jobs.

cd {{release}}

php artisan queue:restart

Linking Storage

You may recall needing to run php artisan storage:link when setting up the project on Forge. Since Envoyer uses a new directory for each deploy, we essentially need to run this on every deploy. Luckily, Envoyer has this functionality built-in. Click on the "Manage Linked Folders" button and fill in the the form.

If you have not modified the default filesystem storage config, then fill in the following:

This corresponds with the filesystem config in your project's repository (config/filesystem.php):

<?php
    // ...
    'links' => [
        //   public/storage            storage/app/public
        public_path('storage') => storage_path('app/public'),
    ],
    // ...

If you're unsure, consult the config file to see which directories you need to link!

Updating the Scheduler

If your project makes use of scheduled tasks, then there is one more thing to do before completing the deployment setup, which is updating the directory where the scheduler runs.

Head over to the "Scheduler" section for the server on Forge and copy the scheduler command for your project:

forge-copy-scheduled-job.png

After copying the command, create a new scheduled job and paste that command into the "Command" field. Make sure that the "User" field matches the user running the existing command as well.

Important: After copying everything over, update the "Command" field so that artisan is run inside the current directory. For example, if your command was previously:

php8.0 /home/veganorg/vegan.org/artisan schedule:run

Change it to:

php8.0 /home/veganorg/vegan.org/current/artisan schedule:run

Create the new job and then delete the old one:

forge-delete-old-scheduled-job.png

Wrapping Up

There's only one more thing we need to do in order to complete the setup, which is updating the directory serving our project.

To make sure the transition is as smooth as possible, deploy the application via Forge first:

envoyer-deploy.png

Once it's done, open the "Meta" tab for your project on Laravel Forge and prepend the "Web Directory" with /current:

forge-update-web-directory.png

Wait a few seconds and then visit the site. If it loads, then you should be good to go! If you get an error, then revert the "Web Directory" change and debug using the logs on Laravel Forge.