Zero-Downtime Deployment with Laravel Forge

Zero-Downtime Deployment with Laravel Forge

Deploying web applications can be a repetitive and error-prone task. In this blog post, we’ll introduce a Bash script designed to automate the deployment process for a web application hosted on a server. Let’s dive into the details of the script and how it streamlines the deployment workflow.

Setup

Before diving into the deployment process, let’s set up some initial configurations:

DOMAIN=example.com
PROJECT_REPO=“your_github_repo_name”
AMOUNT_KEEP_RELEASES=5

RELEASE_NAME=$(date +%s–%Y_%m_%d–%H_%M_%S)
RELEASES_DIRECTORY=~/$DOMAIN/releases
DEPLOYMENT_DIRECTORY=$RELEASES_DIRECTORY/$RELEASE_NAME

These variables define crucial aspects such as the domain name, project repository, and the number of releases to keep.

Deployment Process

Cloning Repository and Setup

The script starts by creating a unique release directory and clones the project repository into it:

cd /home/forge/$DOMAIN

mkdir -p $RELEASES_DIRECTORY && cd $RELEASES_DIRECTORY

git clone $PROJECT_REPO $RELEASE_NAME
cd $RELEASE_NAME
git checkout $FORGE_SITE_BRANCH
git fetch origin $FORGE_SITE_BRANCH
git reset –hard FETCH_HEAD

Environment Setup

Next, the script copies the .env file from the project directory:

printf ‘nℹ️ Copy ./.env filen’
ENV_FILE=~/$DOMAIN/.env
if [ -f $ENV_FILE ]; then
cp $ENV_FILE ./.env
else
printf ‘nError: .env file is missing at %s.’ $ENV_FILE && exit 1
fi

Running Laravel Commands

$FORGE_COMPOSER install –no-dev –no-interaction –prefer-dist –optimize-autoloader

( flock -w 10 9 || exit 1
echo ‘Restarting FPM…’; sudo -S service $FORGE_PHP_FPM reload ) 9>/tmp/fpmlock

if [ -f artisan ]; then
$FORGE_PHP artisan migrate –force
fi

Dependency Installation and Build

The script installs NPM dependencies and generates necessary files:

printf ‘nℹ️ Installing NPM dependencies based on “./package-lock.json”n’
npm install
printf
‘nℹ️ Generating JS App filesn’
npm run build

Linking Deployment Directory

The script links the deployment directory to the current directory:

printf ‘nℹ️ !!! Link Deployment Directory !!!n’
echo $RELEASE_NAME >> $RELEASES_DIRECTORY/.successes
if [ -d ~/$DOMAIN/current ] && [ ! -L ~/$DOMAIN/current ]; then
rm -rf ~/$DOMAIN/current
fi
ln -s -n -f -T $DEPLOYMENT_DIRECTORY/public” ~/$DOMAIN/current

Clean Up

Lastly, the script performs clean-up tasks:

printf ‘nℹ️ Delete failed releases:n’
# Code for deleting failed releases

printf ‘nℹ️ Delete old successful releases:n’
# Code for deleting old successful releases

printf ‘nℹ️ Status – stored releases:n’
# Code for displaying stored releases

printf ‘n✅ Deployment DONE: %sn’ $DEPLOYMENT_DIRECTORY

Full Script

# SETUP #
DOMAIN=yourdomain.com
PROJECT_REPO=“git@github.com:your-team/repo.git”
AMOUNT_KEEP_RELEASES=5

RELEASE_NAME=$(date +%s–%Y_%m_%d–%H_%M_%S)
RELEASES_DIRECTORY=~/$DOMAIN/releases
DEPLOYMENT_DIRECTORY=$RELEASES_DIRECTORY/$RELEASE_NAME

# stop script on error signal (-e) and undefined variables (-u)
set -eu

printf ‘nℹ️ Starting deployment %sn’ $RELEASE_NAME

cd /home/forge/$DOMAIN

mkdir -p $RELEASES_DIRECTORY && cd $RELEASES_DIRECTORY

printf ‘nℹ️ Clone GIT project from %s and checkout branch %sn’ $PROJECT_REPO $FORGE_SITE_BRANCH
git clone $PROJECT_REPO $RELEASE_NAME
cd $RELEASE_NAME
git checkout $FORGE_SITE_BRANCH
git fetch origin $FORGE_SITE_BRANCH
git reset –hard FETCH_HEAD

printf ‘nℹ️ Copy ./.env filen’
ENV_FILE=~/$DOMAIN/.env
if [ -f $ENV_FILE ]; then
cp $ENV_FILE ./.env
else
printf ‘nError: .env file is missing at %s.’ $ENV_FILE && exit 1
fi

$FORGE_COMPOSER install –no-dev –no-interaction –prefer-dist –optimize-autoloader

( flock -w 10 9 || exit 1
echo ‘Restarting FPM…’; sudo -S service $FORGE_PHP_FPM reload ) 9>/tmp/fpmlock

if [ -f artisan ]; then
$FORGE_PHP artisan migrate –force
fi

printf ‘nℹ️ Installing NPM dependencies based on “./package-lock.json”n’
npm install
printf
‘nℹ️ Generating JS App filesn’
npm run build

printf ‘nℹ️ !!! Link Deployment Directory !!!n’
echo $RELEASE_NAME >> $RELEASES_DIRECTORY/.successes
if [ -d ~/$DOMAIN/current ] && [ ! -L ~/$DOMAIN/current ]; then
rm -rf ~/$DOMAIN/current
fi
ln -s -n -f -T $DEPLOYMENT_DIRECTORY/public” ~/$DOMAIN/current

# Clean Up
cd $RELEASES_DIRECTORY

printf ‘nℹ️ Delete failed releases:n’
if grep -qvf .successes <(ls -1)
then
grep -vf .successes <(ls -1)
grep -vf .successes <(ls -1) | xargs rm -rf
else
echo “No failed releases found.”
fi

printf ‘nℹ️ Delete old successful releases:n’
AMOUNT_KEEP_RELEASES=$((AMOUNT_KEEP_RELEASES-1))
LINES_STORED_RELEASES_TO_DELETE=$(find . -maxdepth 1 -mindepth 1 -type d ! -name $RELEASE_NAME -printf ‘%T@t%fn’ | head -n$AMOUNT_KEEP_RELEASES | wc -l)
if [ $LINES_STORED_RELEASES_TO_DELETE != 0 ]; then
find . -maxdepth 1 -mindepth 1 -type d ! -name $RELEASE_NAME -printf ‘%T@t%fn’ | sort -t $’t -g | head -n$AMOUNT_KEEP_RELEASES | cut -d $’t -f 2-
find . -maxdepth 1 -mindepth 1 -type d ! -name $RELEASE_NAME -printf ‘%T@t%fn’ | sort -t $’t -g | head -n$AMOUNT_KEEP_RELEASES | cut -d $’t -f 2- | xargs -I {} sed -i -e ‘/{}/d’ .successes
find . -maxdepth 1 -mindepth 1 -type d ! -name $RELEASE_NAME -printf ‘%T@t%fn’ | sort -t $’t -g | head -n$AMOUNT_KEEP_RELEASES | cut -d $’t -f 2- | xargs rm -rf
else
AMOUNT_KEEP_RELEASES=$((AMOUNT_KEEP_RELEASES+1))
LINES_STORED_RELEASES_TOTAL=$(find . -maxdepth 1 -mindepth 1 -type d -printf ‘%T@t%fn’ | wc -l)
printf ‘There are only %s successfully stored releases, which is less than or equal to yourndefined %s releases to keep, so none of them got deleted.’ $LINES_STORED_RELEASES_TOTAL $AMOUNT_KEEP_RELEASES
fi

printf ‘nℹ️ Status – stored releases:n’
find . -maxdepth 1 -mindepth 1 -type d -printf ‘%T@t%fn’ | sort -nr | cut -f 2-

printf ‘n✅ Deployment DONE: %sn’ $DEPLOYMENT_DIRECTORY

Conclusion

Automation is key to improving deployment efficiency and reducing errors. By using this Bash script, you can streamline the deployment process for your web applications, saving time and ensuring consistency.

Leave a Reply

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