Skip to content Skip to footer navigation

Zero Downtime Deployments

Understanding the folder structure#

Zero downtime deployment services like Laravel Forge, Envoyer and Deployer typically use a multiple-release directory structure and symlinks to handle deployments.

For example, with Laravel Forge:

.env
storage
current -> symlinked to latest release
releases
20220215112950
.env -> symlinked to top level shared .env
storage -> symlinked to top level shared storage
app
routes
etc
20220322153109
20220323180225
20220322153109

Every deployment has its own timestamped release directory, with a fresh clone of the app. The .env file is stored at the top level, and shared between releases using symlinks.

After a successful deployment, the current folder is then symlinked to the latest release. This symlink swap is the secret sauce for zero downtime.

Cache storage#

Statamic's content management heavily relies on caching, and sometimes it's necessary for the Stache to store absolute file paths in your app's cache. This can lead to deployment errors when users are hitting your frontend, since each release exists in a separate timestamped folder.

The solution is simple. Just as you should never share a cache between different websites, you should never share a cache between your deployed releases.

How to avoid sharing file cache#

There are two ways to avoid sharing a file cache between your deployment releases:

  1. Some services, like Laravel Forge, may allow you to configure the "shared paths" between deployments. If your application allows for it, you could remove the storage directory from your site's shared paths, ensuring each release has its own storage folder.
  2. Another option is to create a cache folder at the top level of your app, bypassing the shared storage folder. Configure your app to use a custom cache store location by changing stores.file.path in config/cache.php:
    'stores' => [
    'file' => [
    'driver' => 'file',
    - 'path' => storage_path('framework/cache/data'),
    + 'path' => base_path('cache'),
    ],
    ],

How to avoid sharing Redis cache#

To avoid sharing a Redis cache between your deployment releases, we recommend setting a cache prefix unique to each release on your filesystem. This can be configured by adding a redis.cache.options.prefix in config/database.php:

'redis' => [
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
+ 'options' => [
+ 'prefix' => basename(base_path()).'_',
+ ],
],
],

Git Automation#

If you plan to use Statamic's Git Automation feature alongside zero downtime deployments, you may need to tweak your deployment settings to enable git commits and pushes from each release folder.

Setting up a Git remote#

Hot Tip!

Unlike other services, Laravel Forge will actually keep the .git folder around in each release, meaning you can skip this step.

A troll pointing a teaching stick

Most zero downtime deployment services, like Envoyer and Deployer create releases without a .git folder, which Statamic needs to commit and push content back to your repository.

You can work around this by setting up a Git object right after the Clone New Release step of your deployment process:

git init
git remote add origin [email protected]:your/remote-repository.git
git fetch
git branch --track main origin/main
git reset HEAD

Be sure to modify the above remote to point to your remote repository, along with the branch you wish to track.

Preventing circular deployments#

If you plan on enabling automatic deployment when commits are pushed to your repository, you may wish to selectively disable deployments when Statamic pushes commits back to your repository.

To do this, you will first need to append [BOT] to Statamic's commit messages as documented here. Once this is done, you can add a step to your deployment process to cancel the deployment when the commit message contains [BOT].

if [[ $FORGE_DEPLOY_MESSAGE =~ "[BOT]" ]]; then
echo "AUTO-COMMITTED ON PRODUCTION. NOTHING TO DEPLOY."
exit 0
fi

Ensuring proper deployment hook order#

When adding these steps to your deployment process, you should be mindful of the order in which they happen. Here's the order we recommend:

  • Cancel deployments when commit message contains [BOT]
  • Create release
  • Init Git repository & add Git remote (if necessary)
  • The rest of your deployment script...
Hot Tip!

If you're using Static Caching, make sure you warm the cache after updating the current release, otherwise you'll be warming the wrong cache.

A troll pointing a teaching stick

Committing form submissions#

If you plan on committing form submissions, you will need to store them outside the shared storage directory.

To customize where form submissions are stored, add a form-submissions array to your config/statamic/stache.php config file:

'stores' => [
+ 'form-submissions' => [
+ 'class' => \Statamic\Stache\Stores\SubmissionsStore::class,
+ 'directory' => base_path('forms'),
+ ],
],

After doing this, you will also need to update the tracked path for your submissions in config/statamic/git.php:

'paths' => [
base_path('content'),
base_path('users'),
resource_path('blueprints'),
resource_path('fieldsets'),
resource_path('forms'),
resource_path('users'),
resource_path('preferences.yaml'),
resource_path('sites.yaml'),
- storage_path('forms'),
+ base_path('forms'),
public_path('assets'),
],