Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

plasmic's avatar

Forge Zero Downtime Deployment

Before I start, yes I know I could utilize something like Envoyer for a zero downtime deployment, but it's just not affordable for me at this time to add another subscription to my monthly expenses. I have a deployment script that works pretty well I think, I'm mostly looking for feedback/advice on what I could do better on it, especially as the app grows.

Here is my working script right now:

cd /home/forge

PROJECT_NAME="$FORGE_SITE_PATH"
PROJECT_REPO="[email protected]:my-username/my-repo.git"
RELEASES_KEPT=3
RELEASE=$(date +"%Y-%m-%d-%H-%M-%S")

# Copy the current .env file.
cp $PROJECT_NAME"/.env" $PROJECT_NAME".env"

# Create a project-name-releases directory if it does not exist.
if [ ! -d $PROJECT_NAME"-releases" ]
then
  mkdir $PROJECT_NAME"-releases"
fi

# Create our new release.
cd $PROJECT_NAME"-releases"
git clone $PROJECT_REPO $RELEASE

cd $RELEASE

# Install dependencies
$FORGE_COMPOSER install --no-interaction --prefer-dist --optimize-autoloader

# Move in the current .env file and clean up.
cp -af "../../"$PROJECT_NAME".env" ".env"
rm "../../"$PROJECT_NAME".env"

# Compile our assets.
npm install
npm run build

# Now that our scripts are compiled, we can safely remove the node directory.
rm -rf node_modules

# Copy the current storage directory.
cp -r $"../../"PROJECT_NAME"/storage" "../../"$PROJECT_NAME"-storage"

# Move in the current storage directory and clean up.
rm -rf storage
mv "../../"$PROJECT_NAME"-storage" storage

if [ -f artisan ]
then
  $FORGE_COMPOSER update-permissions # Seed any new permissions
  $FORGE_PHP artisan route:cache
  $FORGE_PHP artisan view:cache
  $FORGE_PHP artisan event:cache
  $FORGE_PHP artisan horizon:terminate
  $FORGE_PHP artisan storage:link
  $FORGE_PHP artisan config:cache
fi

# Clean up old releases.
cd ..
rm -rf `ls -1 | sort -r | tail -n +$((RELEASES_KEPT+1))`

cd /home/forge

# Remove the project directory if it exists.
if [ -d $PROJECT_NAME ]
then
  rm -rf $PROJECT_NAME
fi

# Re-link our new release.
ln -sfn $PROJECT_NAME"-releases/"$RELEASE $PROJECT_NAME

# Reload PHP-FPM.
( flock -w 10 9 || exit 1
    echo 'Restarting FPM...'; sudo -S service $FORGE_PHP_FPM reload ) 9>/tmp/fpmlock

I do have one concern about this script, and it's about the storage directory. I have some custom disks I use for user uploads that are stored in the storage directory. My concern is what if a user happens to be uploading a file at the same time I'm moving the storage directory during a deployment. How do I make sure I don't lose that file in the process? This is mostly why I'm installing and compiling my npm assets before I copy over the storage directory since it can take a while to do that sometimes.

1 like
8 replies
Sinnbeck's avatar

Doesn't forge have that built in? Ploi which is a forge alternative has it as just a checkbox to enable it

plasmic's avatar

As far as I'm aware, forge doesn't offer this. You can do automatic deployment when you push to your repository, but it's not really the same thing.

Sinnbeck's avatar
Sinnbeck
Best Answer
Level 102

Your script looks fine. I would probably do a shallow clone (1 level deep) and I would symlink env and storage instead of copying.

plasmic's avatar

@sinnbeck Thanks for your replies.

For the shallow clone, do you mean something like this?

git clone -b $FORGE_SITE_BRANCH --depth 1 $PROJECT_REPO $RELEASE

And then for symlinking, I'm assuming I'd keep a master copy of those outside somewhere and symlink to them after cloning? Do you know how that would affect using the local storage driver in Laravel if my storage directory is symlinked?

Sinnbeck's avatar

@plasmic yeah exactly. And regarding storage and env, it works perfectly. I've done this in the past myself, before switching to ploi

plasmic's avatar

Your right; I just got done testing a deployment on a test directory and the symlinks work nicely. I'll definitely have to look into ploi at some point even though I still like Forge a lot for what it offers.

dam-man's avatar

Moving the storage folder over and over csn go wrong. The more storage you get, the more time it cost. And, what if someone uploads a new file during the deploy.

I suggest to symlink the storage folders and env file also. Then the deploys goes probably betrer. I am doing the same at my ploi account as ploi is also doing a copy of the storage folder which went wring a few times.

Another tip: replace ../../ parts with a variable. This fives more flexibillity.

Please or to participate in this conversation.