Zero-downtime Laravel deploys with this simple bash script
This is a quick and simple bash script which allows you to deploy Laravel projects with zero downtime and even rollbacks.
I made this for a nice and simple way to deploy my own Laravel projects without the need for a 3rd party service. I even use it to deploy this blog.
Assumptions & pre-requisites
- You have a Laravel project on GitHub
- You want to deploy it to a Linux server
- You have SSH access to the server
- You have a basic understanding of editing files via the command-line with something like Vim
Usage
Once set up, simply SSH into your server and type deploy
.
Starting off
We're going to create the deploy
script inside a bin
folder under your username.
SSH into your server and run the following:
mkdir ~/bin
touch ~/bin/deploy
You'll also need to update your .bash_profile
with the path to your bin
folder, so you can run the deploy
command from anywhere.
Just run the following:
echo 'export PATH=/path/to/your/bin:$PATH' >>~/.bash_profile
source ~/.bash_profile
Making the script executable
We now have an empty script file. We need to make the file executable.
chmod u+x ~/bin/deploy
Before we add the script
Personal Access Token
You'll need a Personal Access Token from GitHub to be able to clone your repository. If you don't have one, you can do this by going into GitHub, clicking your profile picture in the top right > Settings > Developer Settings > Personal Access Tokens > Generate New Token.
.env file
You'll also need a base .env
file sat one level above your project directory.
Note: With the script I am assuming your project's document root is /var/www/vhosts/myproject/current/public
. Please ensure to change this accordingly.
So make sure to add your production .env
file to /var/www/vhosts/myproject
.
cd /var/www/vhosts/myproject
vim .env
then paste your contents accordingly.
Your project folder
The script works best with an empty project that hasn't been deployed yet. The reason for this is we use a symbolic link to the document root, rather than putting the files there directly. You can get around this however by running mv current current-backup
before the deploy (keep in mind this will bring down your site until you re-deploy).
Adding the script
We're ready to add the script. cd ~/bin
and then vim deploy
to open the empty script file and add the following:
#!/usr/bin/bash
NUM_RELEASES=4
PERSONAL_ACCESS_TOKEN=
GIT_USERNAME=
REPO_NAME=
echo "Deploying..."
cd /var/www/vhosts/myproject/
if [ -d releases ]; then
cd releases
else
mkdir releases && cd releases
fi
echo "Removing old releases..."
INDEX=0
FOLDERS=`ls | wc -l`
if [ $FOLDERS -ge $NUM_RELEASES ]; then
for d in */ ; do
if [ $INDEX -le $(( $FOLDERS - $NUM_RELEASES )) ]; then
rm -rf $d
fi
INDEX=$(( INDEX + 1 ))
done
fi
echo "Cloning repository..."
NEWRELEASE=$(date +%Y%m%d%H%M%S)
git clone https://$PERSONAL_ACCESS_TOKEN@github.com/$GIT_USERNAME/$REPO_NAME.git $NEWRELEASE
cp ../.env $NEWRELEASE/.env
cd $NEWRELEASE
echo "Installing composer dependencies..."
composer install --optimize-autoloader --no-dev --no-interaction --prefer-dist
echo "Installing npm dependencies..."
npm install --no-audit --prefer-offline
echo "Compiling front-end assets..."
npm run prod
echo "Running artisan commands..."
php artisan migrate --force
php artisan storage:link
php artisan optimize
php artisan queue:restart
cd ../..
echo "Activating new release..."
rm current && ln -s releases/$NEWRELEASE current
echo "Deploy complete!"
Feel free to fill in the variables here with the correct values, and tweak the artisan commands to fit your use-case.
And that's it! Simply run the command deploy
and you're off and running.
How it works
The zero-downtime is achieved by using a symbolic link from the document root to the most recent release.
So when you deploy, it builds a new release in a separate directory, then once everything is finished, it simply updates the symbolic link to point at the new directory instead. This happens pretty much instantly, so it's a seamless experience for your users.
The script keeps up to 4 releases in your releases
folder at any time. You can of course adjust this in the script to keep more or less if you like.
Rolling back
If for any reason you need to roll back a deploy to a previous one - for example if there's a bug in production - you can do so in the following way:
cd /var/www/vhosts/myproject/releases && ls -lah
Find the folder name of the release you want to roll back to (probably the second one down in the list)
cd ../
rm current && ln -s releases/{folderName} current
Of course, you'll need to have deployed at least twice to be able to roll back to a previous version. If there's enough demand for this I'll create a separate rollback
script.
There it is! Hope you like it - please drop me an email to tom@tomn.dev or on Twitter @thetomnewton with your thoughts and feedback on how to improve it even more.