Deployments - Deploynix Docs

Deployments

How Deploynix ships code to your servers — triggers, the release lifecycle, the on-disk layout, rollback, and auto-deploy via Git webhooks.

Overview

A deployment is one run of a site's deploy pipeline. Deploynix clones your latest code into a fresh release directory, symlinks shared files into it, runs the default deploy script followed by any custom commands you added, and — if zero-downtime is enabled — swaps a single symlink to make the new release live. You can watch the whole thing stream in real time from the site's page.

How Deployments Are Triggered

Any of the following kicks off a deployment:

  • Manual deploy button on the site's page — rate-limited to protect against accidental double-clicks.
  • Git webhook — a push to the configured branch of a connected repository. Deploynix verifies the signature, checks the branch matches the site's branch, and records a SiteWebhook row with the commit hash, message, and pusher before queueing the deploy.
  • Scheduled deploy — pick a future date and time from the site page; the deploy fires automatically at that moment. You can cancel a scheduled deploy until it runs.
  • API call — POST to the site's deploy endpoint with a Sanctum token that has the site:deploy ability (see API Reference).

The Deploy Lifecycle

With zero-downtime enabled, every deployment follows the same ordered pipeline. Each step streams its output live over WebSockets so you can watch it happen.

  1. Create a release directory at releases/{timestamp} inside the site root.
  2. Shallow-clone the configured branch of the site's Git repository into that directory.
  3. Symlink shared resources into the release — .env, the Laravel storage/ tree, and an SQLite database file if the site uses one.
  4. Write the site's environment variables from the database into shared/.env so the release sees the current values.
  5. Run the default deploy script — Composer install, npm build (when a package.json is present), Laravel migrations, config:cache, and a graceful queue restart.
  6. Run your custom deploy script if you added one on the Deploy Script tab.
  7. Re-sync env and re-run config caching — in case your custom script copied a new .env.example or touched config files.
  8. Swap the current symlink atomically to the new release (ln -sfn — a single syscall, zero downtime).
  9. Reload PHP-FPM (or Octane, if the site uses it) so the new code is serving requests.
  10. Prune old releases beyond the configured retention count.

Sites without zero-downtime deploy follow the same steps in place, skipping the release directory and symlink swap.

Zero-Downtime Releases

The on-disk layout of a zero-downtime site looks like this:

/home/deploynix/example.com/
├── current/              → symlink to releases/20260416123456
├── releases/
│   ├── 20260416123456/   (active)
│   ├── 20260416120000/
│   └── 20260416110000/
└── shared/
    ├── .env
    └── storage/
        ├── app/
        ├── logs/
        └── framework/

Shared directories (storage, logs, framework) and shared files (.env, an optional SQLite file) are symlinked into each new release so uploaded files, cached views, session files, and secrets survive across deploys. The current symlink is what Nginx and PHP-FPM serve — swapping it is the instant cut-over that makes the new release live.

Retention is controlled by the site's Releases to keep setting (1–20, default 5). Once a new release activates, older releases past that limit are deleted.

The Deploy Script

Every site has two layers of deploy logic: a default script that Deploynix generates and runs for you, and a custom script you can edit on the site's Deploy Script tab. Both run with the working directory set to the release being built, and with CI=true exported so interactive tools stay quiet.

Default Script

The default script is tailored to the project but for a typical Laravel site it runs:

composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev
# if package.json exists:
npm ci && npm run build
# if artisan exists:
php artisan key:generate --force
php artisan migrate --force
php artisan config:cache
php artisan queue:restart
php artisan horizon:terminate

You don't edit the default script directly — it reflects best practice for the project type and runs automatically.

Custom Commands

Your custom script runs after the default script, in the release directory. Use it to:

  • Warm application caches (php artisan route:cache, php artisan view:cache).
  • Reindex search (php artisan scout:import).
  • Run database seeders for a new environment.
  • Notify Slack, PagerDuty, or your error tracker that a release shipped.
  • Restart extra daemons that aren't managed as site queue workers.

Live Output

Deployments stream their output over Laravel Reverb (WebSockets) to a terminal panel on the site page. You see each line — stdout, stderr, status messages — the moment the server emits it, which makes failures obvious. The stream is live-only: once the deployment finishes, the terminal stays filled with the last run's output until the next deploy replaces it.

Rollbacks

Because every zero-downtime deploy keeps the previous N releases on disk, rollback is effectively free:

  1. Open the site's Releases tab.
  2. Click Rollback next to any previous release.
  3. Deploynix re-points the current symlink at the selected release and reloads PHP-FPM.

That's the whole operation — one symlink swap and a reload. You can roll back to any release still on disk, not just the immediately previous one.

Rollback Caveat

Rolling back restores code, not data. If a release ran a destructive migration, roll back the migration manually or restore a database backup before (or after) the rollback.

Auto-Deploy on Push

Connecting a Git repository over GitHub, GitLab, or Bitbucket OAuth registers a webhook automatically. Enable Auto Deploy on the repository under Settings → Git Repositories and every push to a site's configured branch will deploy it.

When a webhook arrives, Deploynix:

  1. Verifies the request signature using the secret stored for that repository.
  2. Parses the event, branch, commit hash, commit message, and pusher.
  3. Compares the pushed branch to each site's configured branch.
  4. For every matching site, records a SiteWebhook row and queues the deploy.
  5. For non-matching branches, records the webhook as skipped so you can see it came in.

Webhook history gives you an audit trail of which pushes triggered which deploys — including the commit metadata. See Git Integration for provider-specific setup.

Scheduled Deploys

From the site header, open the deploy menu and pick Schedule deploy. Choose a future date and time; Deploynix stores the moment and runs the deploy when it arrives. Until then, the site shows a pending-deploy badge and a Cancel scheduled deploy button. Use this for maintenance windows, coordinated releases across multiple sites, or quiet-hour cut-overs.

What Happens When a Deploy Fails

If any step in the pipeline exits non-zero, the deployment is aborted immediately:

  • The current symlink is not swapped, so the previous release keeps serving traffic.
  • The failed release directory stays on disk so you can inspect it over SSH.
  • The site's status message records the error, and the live terminal holds the last output for debugging.
  • Webhook-triggered deploys mark the SiteWebhook row as failed; the underlying job is retried a limited number of times before giving up.

Fix the issue, push a new commit (or hit Deploy again), and the next release starts from scratch in a new directory.

Environment Inside the Script

When your deploy script runs, it sees the site's environment variables via the symlinked .env and has CI=true exported by Deploynix. The working directory is the release being built, so relative paths like ./artisan, ./vendor/bin/phpunit, or ./node_modules/.bin/tsc all resolve against the new code. If you need to reach files outside the release (uploaded media, for example), reach into the site's shared/ directory with an absolute path.