Creating a decent Laravel deploy script

A good deploy script can save you time and speed up you application, and it only takes a few minutes to set one up.

I have a standard deploy script which I use for almost all of my projects, which I'm going to break down and share with you.

Note: This article assumes that you're using Laravel Forge. If you're not, many of the settings will be applicable, but the setup steps may be a bit different.

Just give me the deploy script

What is a deploy script?

A deploy script is file which contains a bunch of commands (a shell script) which all get executed when you make a deployment, which is usually whenever you push code changes to your project. It makes sense that you might have a list of tasks you want performed when you do a deployment, such as clearing caches or running any new database migrations - this is exactly what a deploy script is for.

If you're using Forge, a deploy script is already set up for you and does a few things out of the box. You can find it under Site Details > Apps. We're going to take that deploy script and supercharge it.

Maintenance mode

Putting the application into maintenance mode while the new version is deployed is a good idea. Your deploy script could sometimes take a minute or so to complete and it's better to show that the site is updating rather than a potentially half broken version of the site.

At the start of the deploy script, we'll turn on maintenance mode, and at the end we'll turn it off again.

# Turn on maintenance mode
php artisan down

# ... Deploy script stuff

# Turn off maintenance mode
php artisan up

You can customize the message which is displayed by defining your own template at resources/views/errors/503.blade.php.

Clearing caches

There's a good chance that the changes in your deployment will invalidate items stored in the cache on your server. It's a good chance to clear them out at this point.

php artisan cache:clear

Clearing and optimising routes

Using route caching can drastically speed up your applications' performance. The artisan command route:cache will serialize the results of your routes.php file meaning that it doesn't have to be re-computed on every page load.

As part of our deploy script we first run route:clear to clear out the existing route cache, before running route:cache.

# Clear and cache routes
php artisan route:clear
php artisan route:cache

Note that this is only compatible with controller based routing. So if you're using any closures in your routes.php file, I'm afraid you're out of luck.

Clearing and optimising the config

As well as caching routes, we can also use configuration caching to cache the config files. The performance impact here isn't as great, but it's still worth doing. Essentially all of the config files are read, serialized and then stored in a single config file which can be loaded by the framework. Thus reducing the number of file read operations from tens to one.

Again, we clear out the existing cache before creating the new one.

# Clear and cache config
php artisan config:clear
php artisan config:cache

Clear expired password reset tokens

When a user requests a password reset, a reset token is created in the password_resets database table. If the user never clicks on the link they receive in the email then these just hand around forever, unless you run auth:clear-resets. I add it to the deploy script to stop the database getting clogged up with all the unused tokens.

# Clear expired password reset tokens
php artisan auth:clear-resets

Compiling front-end assets (optional)

While I'm developing, I'm going to be compiling my assets using a watcher like Mix's npm run watch. It's fast, but it's not optimised for performance. Instead of running npm run production before every deployment, we can get the server to do it. All we need to do is install the dependencies using npm install and then run npm run production.

The only downside to this is that it can slow down your deployment process.

# Install node modules
npm install

# Build assets using Laravel Mix
npm run production

You can of course do this with the build tool of your choice e.g. Grunt, Gulp, webpack etc.

I keep this commented out by default as I don't always want to have this run on my server at deploy time. Instead, I will build the assets locally and check them into source control so that they're uploaded with the reset of the changed files.

Putting it all together

Combined with the Forge default deploy script we now end up with something like this:

# Change to the project directory
cd /home/forge/domain.com

# Turn on maintenance mode
php artisan down

# Pull the latest changes from the git repository
git pull origin master

# Install/update composer dependecies
composer install --no-interaction --prefer-dist --optimize-autoloader

# Run database migrations
php artisan migrate --force

# Clear caches
php artisan cache:clear

# Clear expired password reset tokens
php artisan auth:clear-resets

# Clear and cache routes
php artisan route:clear
php artisan route:cache

# Clear and cache config
php artisan config:clear
php artisan config:cache

# Install node modules
# npm install

# Build assets using Laravel Mix
# npm run production

# Turn off maintenance mode
php artisan up

I've put this up on Github as a Gist.