Skip to content

Deploy Steps

A project’s deploy pipeline is an ordered list of steps that run against every configured server (or a subset, if a step targets specific servers). There are two kinds of step:

  • Builtin steps (clone, composer, activate, purge) implement the core mechanics of a zero-downtime deploy: extracting the release, symlinking shared storage, atomically activating, and cleaning up old releases.
  • Custom steps run a shell script you write, over SSH, in the release directory (or wherever you point it with placeholders).

New projects are seeded with a bare-minimum, zero-downtime pipeline:

Order Step Type
10 Clone New Release builtin (clone)
20 Activate New Release builtin (activate)
30 Purge Old Releases builtin (purge)

This alone is a working pipeline: it downloads, extracts, and activates your code. For a real Laravel app you’ll almost always add custom steps for Composer, migrations, cache warming, and queue restarts. Add them from Add Custom Step, using the predefined library below to prefill common ones, and drag them into place between Clone and Activate (or after Activate, for anything that should run against the live release, like queue restarts).

Steps run strictly in sort order, sequentially, across all target servers before moving to the next step.

  • Reordering: drag any step (builtin or custom) by its handle on the Deploy Steps tab; the new order is saved immediately.
  • Editing and deleting: only custom steps can be edited or removed. The four builtin steps (clone, composer, activate, purge) can be reordered but not edited or deleted through the UI, since they implement the mechanics the rest of the pipeline depends on.
  • Adding: click Add Custom Step. You can start from a predefined command (Composer, npm build, migrate, storage:link, Laravel optimize, Filament optimize, restart Horizon, restart queue workers) which prefills the name and script, or write your own from scratch.

Custom step scripts can use these placeholders, expanded at deploy time:

Placeholder Expands to
{{release}} This deployment’s release directory, e.g. /home/website/project.com/releases/20251018132019
{{current}} The live current symlink, e.g. /home/website/project.com/current
{{deploy_path}} The base deploy path, e.g. /home/website/project.com
Terminal window
cd {{release}} && php artisan migrate --force
cd {{current}} && php artisan horizon:terminate

{{current}} is useful for commands that should always act on whatever release is currently live (like restarting queue workers or Horizon), as opposed to {{release}}, which is the release being deployed right now. The two only differ during the deploy itself, before activation.

Every step, builtin or custom, has an optional Run As field. When set, the step’s command runs via non-interactive sudo as that user:

Terminal window
sudo -n -u deploy bash -c '<your command>'

Leave it blank to run as the SSH user you connect with. Using Run As requires the SSH user to have passwordless sudo configured for that target user on the server.

Both builtin steps that touch the filesystem and custom steps can be scoped to a subset of servers (leave unchecked to run on all servers). This is useful for steps that should only run once, like queue restarts on a dedicated worker box.

The clone step symlinks the shared .env and storage into the new release directory immediately after extraction, not at activation time. That means php artisan commands that need config, a database connection, or writable storage (migrations, cache warming) can run safely against the new release before current is swapped to point at it. The activate step re-links .env/storage too, but by then it’s just an idempotent safety net.

Queue workers are restarted after activation, so they pick up the newly-live code from current rather than the release that’s about to be replaced.

See How Deployments Work for the full six-phase flow and the on-server directory layout.