Write and test a deploy script

llms.txt

Every deployment does the same three things: fetch the code, run your deploy script, then make the new version live. The first and the last are done by SrvYard. The middle one is yours: the commands that install dependencies, build assets, migrate the database and clear caches. When a site is created SrvYard writes a sensible script for its type, and you edit it whenever your application needs something different.

This article shows where to find the script, explains exactly how it runs, walks through the default Laravel script line by line, and covers the settings around it: push to deploy, the deploy hook, how many releases are kept and the health check. It ends with the mistakes that cause most failed deployments.

Before you begin

  • A site with a repository. Deployments fetch code from the repository you chose when you created the site. A site without one still has a script, but there is nothing to clone.
  • The right permission. Members who can only view a site see the Deployments page and the script, but the Deploy now button and the editor's save buttons are not available to them. Ask an owner or admin for a role that can manage the site.
  • A rough idea of what your app needs. Which package manager builds the front end, whether the app has migrations, whether it runs queue workers. The default script already covers a standard Laravel application.

1. Find the Deployments tab

Open the site and choose Deployments.

The Deployments tab of a site
Source, deploy hook, a shortcut to the deployment settings and the history of every deployment.
  1. Deploy now starts a deployment of the configured branch. If another deployment of the same site is running, the new one waits its turn instead of running alongside it.
  2. The Deploy hook card holds the address you can request from CI to start a deployment (more on that in step 4).
  3. Open deployment settings goes to the site settings, where the deploy script and the rest of the deployment configuration live.
  4. The History table lists every deployment, newest first, with its status, commit, trigger, release name, start time and duration. The row menu offers View deployment, View output and Roll back to this.

The Source card above shows the repository, branch, strategy (for example Zero downtime) and how many releases are kept. The Push to deploy switch is here too, so you can turn it on or off without leaving the page.

2. Open and read the deploy script

Press Open deployment settings, or open the site's Settings tab and choose Deployments in the side menu.

The Deploy script card
The script editor with the .env switch and the Reset to default and Save script buttons.
  1. The editor. A shell script with line numbers. Macros and comments are highlighted.
  2. Make .env variables available to the deployment script. Loads the site's .env into the script's environment. Values are never printed or logged. Off by default.
  3. Reset to default. Replaces the script with the one SrvYard generates for this site type and settings. Nothing is saved until you press Save script.
  4. Save script. Saves the script. Saving does not deploy. The next deployment, manual or automatic, uses it.

The script runs as the site's Linux user, in the application directory of the new release (the release folder plus the site's root directory), with the site's PHP and Composer first on the path. It is run with bash -e: the first command that fails stops the script, and the deployment is marked as failed. A script that does not finish within 780 seconds is stopped and the deployment fails with a timeout message.

The default Laravel script, line by line

For a zero-downtime Laravel site the script SrvYard writes is:

$CREATE_RELEASE()

cd "$SRVYARD_RELEASE_DIRECTORY"

composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev

if [ -f package.json ]; then
    if command -v npm >/dev/null 2>&1; then
        npm ci
        npm run build
    else
        echo "npm is not installed on this server, so the front-end assets were not built."
    fi
fi

php artisan optimize
php artisan storage:link --force
php artisan migrate --force

$ACTIVATE_RELEASE()

$RESTART_QUEUES()
  • $CREATE_RELEASE() marks the start of the new release. SrvYard has already fetched the code into a new folder under releases/ by the time your script starts, so this is a marker rather than an action.
  • cd "$SRVYARD_RELEASE_DIRECTORY" moves into the new release. The variable always points at the folder being built, not at the live one.
  • composer install ... --no-dev installs the locked production dependencies without asking questions. Remove --no-dev only if the site really needs development packages.
  • The package.json block installs front-end dependencies with npm ci and builds with npm run build. The package manager and build command come from the site's advanced settings. If the package manager is missing on the server, the script prints a message and carries on instead of failing, so a front end that never built is easy to miss: read the log.
  • php artisan optimize caches the configuration, routes and views. storage:link makes public/storage point at the shared storage folder. migrate --force runs pending migrations without the production confirmation prompt.
  • $ACTIVATE_RELEASE() is the moment the site switches to the new version. SrvYard repoints the current link at the new release in one atomic step, so visitors never see a half-built release. Anything that fails before this line leaves the live site on the previous release (a migration that already ran stays applied).
  • $RESTART_QUEUES() runs php artisan queue:restart and, when Laravel Horizon is installed, horizon:terminate, so workers pick up the new code.

A site that does not use zero downtime gets a shorter script without the macros. It runs in place: Composer install, the front-end build, storage:link, migrate, optimize and srvyard_restart_queues. The code is updated in the site folder while the site is being served, which is why zero downtime is the recommended setting.

3. Use the macros, variables and functions

The macros and variables note
Everything the script can rely on is listed under the editor.
  1. Macros and variables. $CREATE_RELEASE(), $ACTIVATE_RELEASE() and $RESTART_QUEUES() are expanded before the script runs. They are not shell syntax, so do not put them in quotes or in if conditions. Scripts copied from other panels keep working: $FORGE_PHP, $FORGE_COMPOSER and $FORGE_RELEASE_DIRECTORY are accepted, and every variable also exists with the prefix $SRVYARD_.
  2. The time limit. The script is stopped after 780 seconds.

The functions srvyard_restart_queues and srvyard_reload_php are available in the shell. The environment the script sees includes:

Variable What it holds
SRVYARD_RELEASE_DIRECTORY The folder of the release being built
SRVYARD_SITE_PATH, SRVYARD_SITE_ROOT The live path of the site and its document root
SRVYARD_SITE_USER, SRVYARD_SITE_BRANCH The Linux user and the configured branch
SRVYARD_PHP, SRVYARD_PHP_FPM, SRVYARD_COMPOSER The PHP binary of the site, its FPM service name and Composer
SRVYARD_DEPLOY_COMMIT, _AUTHOR, _MESSAGE The commit being deployed
SRVYARD_DEPLOYMENT_ID The id of this deployment
SRVYARD_MANUAL_DEPLOY, SRVYARD_QUICK_DEPLOY 1 or 0: started by a person, or by a push or the deploy hook
Tip

The script runs on every deployment. Put a command that must only happen once, such as a data fix, into a migration instead, or guard it with a check so running it twice is harmless.

Common additions

# Stop early, with a clear message, when a PHP extension the app needs is missing
php -m | grep -qi '^redis$' || { echo "The redis PHP extension is not installed" >&2; exit 1; }

# Remove a cache the build must not reuse
php artisan cache:clear

# Tell your team (a failing curl fails the deployment, so add || true)
curl -fsS -X POST "https://hooks.example.test/deploys" -d "commit=$SRVYARD_DEPLOY_COMMIT" || true

Put build and check commands before $ACTIVATE_RELEASE(): a failure there protects the live site. Put "tell someone" commands after it.

4. Start deployments from a push or from CI

Push to deploy and the deploy hook URL
The settings page: the switch, the hook address and its Regenerate URL button.
  1. Push to deploy. When on, SrvYard registers a webhook at your git host and every push to the configured branch starts a deployment. Turning it off removes the webhook.
  2. Deploy hook URL. Request it with GET or POST from CI or any script to start a deployment. Add ?branch=main to ignore requests that are not for that branch.
  3. Regenerate URL. Creates a new address and retires the old one. Do this if the address was ever pasted somewhere it should not be: anyone who has it can start a deployment.

The hook address is a secret. Store it as a secret in your CI system, never in the repository.

curl -fsS "https://your-srvyard-host/hooks/deploy/<token>?branch=main"

5. Keep releases and check the site after a deploy

Retention and health checks
Deployments to keep and the health check.
  1. Deployments to keep. How many releases stay on the server after each successful deployment, from 1 to 50. The live release and the one before it are never removed, so a rollback is always possible. A lower number takes effect, and older releases are deleted, at the next deployment.
  2. Check the site after each deployment. After the new release is live SrvYard requests a URL on your site. Any answer from 200 to 399 passes. SrvYard tries up to three times, a few seconds apart.
  3. URL or path. A path such as /up, or a full URL on one of the site's own domains. Empty checks the home page.

A failing health check marks the deployment as failed but does not roll back. If the new release is broken, use Roll back to this on an earlier deployment in the history.

6. Test a script change

Edit, save and then watch one deployment:

  1. Change the script and press Save script.
  2. Go back to the Deployments tab and press Deploy now.
  3. Open the new row with View deployment.
A finished deployment
The details of a deployment and its log.
  1. The header shows the commit, the author, what triggered the deployment, the start time, the duration and the release name. Live means this is the release the site serves now.
  2. Running the deploy script marks where your script starts. Each command is printed after a $, followed by its output.
  3. Deployment finished. is the last line of a successful deployment. When it fails, the last lines of the log say which command failed and its exit status.

The page has two panels, Build logs and Deployment logs; open Deployment logs to read the whole run. A failed deployment keeps its log, so you can read it later, and Collapse all folds the panels.

Note

Rolling back does not undo database migrations. If the release you are leaving added a column, the older code runs against the newer schema. Make migrations backwards compatible, or write the rollback by hand.

Troubleshooting

  • "The deploy script exited with status 1." The command just above the error in the log failed. Run it by hand on the server as the site user, from the release folder, to see the full error.
  • "The deploy script did not finish within 780 seconds and was stopped." A step is hanging or genuinely slow (a large npm ci, a database migration). Move one-off heavy work out of the deploy, or build assets in CI.
  • composer: command not found or npm: command not found. Composer is available to PHP sites. Node and npm come from the server's Runtimes tab: install them there, or give the site a Node version of its own.
  • A command works over SSH but fails in the script. The script has a tiny environment: only the variables listed above, the site's PHP and the system path. It does not read your shell profile. Use full paths or export what you need at the top of the script.
  • The script needs a value from .env. Turn on Make .env variables available to the deployment script. Without it .env values are not in the environment, although artisan commands still read the file themselves.
  • Deployment succeeded but the site shows the old version. Check that $ACTIVATE_RELEASE() is still in the script. If you removed it, the new release is built but never made live.
  • The health check fails right after a good deployment. PHP-FPM and queues may need a moment, or the check URL needs a login. Point it at a public path such as /up.
  • Two deployments started at once. They do not run in parallel. The second waits for the first.
Last updated 2026-10-2411 min readNeed help? Contact support