PHPackages                             slipstream/slipstream - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [DevOps &amp; Deployment](/categories/devops)
4. /
5. slipstream/slipstream

ActiveLibrary[DevOps &amp; Deployment](/categories/devops)

slipstream/slipstream
=====================

Open-source zero-downtime deploy platform for Laravel (Envoyer-style, no SSH)

v0.1.0(yesterday)00MITPHPPHP ^8.2

Since Jul 27Pushed yesterdayCompare

[ Source](https://github.com/forthmediallc/SlipStream)[ Packagist](https://packagist.org/packages/slipstream/slipstream)[ Docs](https://github.com/forthmediallc/SlipStream)[ RSS](/packages/slipstream-slipstream/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (12)Versions (2)Used By (0)

Slipstream
==========

[](#slipstream)

Zero-downtime deploys for Laravel — self-hosted, no SSH to your servers.

Slipstream installs into your app. On push (or from the dashboard), each server builds a fresh release, runs health checks, then atomically swaps traffic to the new code. Failed builds never go live. A gated dashboard shows fleet status, live logs, webhooks, and settings.

Requirements
------------

[](#requirements)

- PHP 8.2+ (8.3+ recommended for Laravel 13)
- Laravel 11, 12, or 13
- Livewire 3.5+ or 4.x
- Linux in production (`symlink` swap, `systemctl`, `setsid`)
- Shared database across servers (multi-server); cache driver that supports locks (Redis or database) for fleet migrations

Installation
------------

[](#installation)

```
composer require slipstream/slipstream
php artisan slipstream:install
```

### Authorize the dashboard

[](#authorize-the-dashboard)

The dashboard is **closed by default in production** and only open automatically in the `local` environment. You decide who gets in one of two ways:

**Option A — an authorization callback** (register in `AppServiceProvider::boot()`):

```
// AppServiceProvider::boot()
use Slipstream\Slipstream;

Slipstream::auth(function ($request) {
    return $request->user()?->isAdmin();
});
```

**Option B — a `viewSlipstream` gate** (if you already organize access with gates/policies):

```
// AppServiceProvider::boot()
use Illuminate\Support\Facades\Gate;

Gate::define('viewSlipstream', fn ($user) => $user->isAdmin());
```

`Slipstream::check($request)` resolves access in this order: the `Slipstream::auth()` callback if set → otherwise a `viewSlipstream` gate if one is defined → otherwise **`local`, from the machine itself only**. The package never defines the gate itself, so nothing is exposed until you opt in.

That last fallback is narrow on purpose: the dashboard grants shell (deploy hook scripts run on the server), the shared `.env`, and the webhook secret. `local` alone is not a fence when the dev server is bound to `0.0.0.0`, published out of a container, or exposed through a share tunnel (`herd share`, ngrok, expose) — so the request must also arrive unproxied from loopback. Set `SLIPSTREAM_LOCAL_OPEN=true` if you genuinely need the old any-origin behaviour in `local`; registering a gate is better.

**How the gate is enforced (defence in depth).** The check runs at three layers, so a mutation can never be driven from a stale page without re-passing it:

1. **Route middleware** (`AuthorizeSlipstream`) guards the initial full-page `GET` of every dashboard route.
2. **Livewire persistent middleware** re-applies the same gate on every `POST /livewire/update`, so component actions can't run from a captured snapshot.
3. **Per-component `boot()`** re-checks on every hydration/action (deploy, rollback, hook edits, `.env` writes/pushes, secret reveal) as a final backstop.

**The exception — hook endpoints.** `/hooks/deploy`, `/hooks/relay`, and `/hooks/env-sync` are intentionally *not* behind the dashboard gate: they are unauthenticated, machine-to-machine POSTs verified by HMAC signature/secret (see [Security](#security)). Everything else requires passing the gate above.

Optional pre-swap checks (throw on failure):

```
Slipstream::healthCheck('custom', function () {
    // …
});
```

### Config

[](#config)

Published as `config/slipstream.php`:

OptionPurpose`path`Dashboard URL segment (default `slipstream`)`domain`Optional subdomain for the dashboard`middleware`Route middleware (default `['web']`)`local_open`Allow any origin in `local` when no gate is set (`SLIPSTREAM_LOCAL_OPEN`, default off)`webhook.allow_redeploy_same_sha`Let a webhook redeploy the commit already live here (default off)`server_name`This node’s fleet identity (`SLIPSTREAM_SERVER`)`views.*`Enable/disable dashboard sections`deploy.keep_releases`How many releases to retain (default 5)Re-publish after package upgrades:

```
php artisan slipstream:publish              # config + assets
php artisan slipstream:publish --config
php artisan slipstream:publish --assets
```

First deploy (production layout)
--------------------------------

[](#first-deploy-production-layout)

Slipstream uses a release layout on disk:

```
{app_root}/
  .env                 # shared
  storage/             # shared
  repo.git/            # bare mirror
  releases/_/
  current -> releases/…

```

The layout root is auto-detected (`current/` or `releases/*` → parent; otherwise `base_path()`). Override with `SLIPSTREAM_APP_ROOT` only if needed.

```
php artisan slipstream:provision --repo=git@github.com:org/app.git
# Create the shared .env at the layout root once (production values, APP_KEY, etc.)
php artisan slipstream:deploy
```

Point your web server document root at `{app_root}/current/public`.

Ensure the scheduler runs (heartbeats and future scheduled work):

```
* * * * * cd /path/to/current && php artisan schedule:run >> /dev/null 2>&1
```

Webhooks
--------

[](#webhooks)

Dashboard → **Webhook** shows the endpoint and secret for this server.

- **URL:** `POST https://your-app/slipstream/hooks/deploy` (path is configurable)
- **GitHub:** push events, secret = HMAC (`X-Hub-Signature-256`)
- **GitLab:** Push Hook, secret = token (`X-Gitlab-Token`)
- **Bitbucket:** repo:push, secret = HMAC (`X-Hub-Signature`)

### One webhook for the whole fleet

[](#one-webhook-for-the-whole-fleet)

1. On each peer, set **Servers → Edit → Base URL** (directly addressable origin).
2. On the node that receives the git webhook, enable **Relay mode**.
3. That node fans out a signed request to each peer’s `/hooks/relay`.

Peer traffic (relay + env-sync nudges) is signed with the **fleet key**, not the webhook secret — see [Two secrets](#two-secrets).

Without relay, add the same webhook URL once per server (each must be reachable without a single shared load-balanced VIP stealing the hook).

Dashboard
---------

[](#dashboard)

Default: `/slipstream`

SectionWhat it does**Deployments**Fleet timeline, deploy now, rollback, live logs**Servers**Heartbeats, pause/resume, drift, per-server services**Pipelines**Branch patterns, auto-deploy, which servers participate**Webhook**Endpoint, secret, delivery log, relay**Settings**Repo, keep-N, shared paths, notifications, hooks**Environment**Guarded editor for the shared `.env` — view/edit any server’s file and push to this server, selected servers, or the whole fleetRollback is **code only** — it does not reverse database migrations. Prefer expand→contract migrations.

Artisan commands
----------------

[](#artisan-commands)

CommandPurpose`slipstream:install`Publish assets, migrate, seed defaults`slipstream:publish`Re-publish config / assets / migrations`slipstream:provision`Deploy key, bare mirror, directory layout`slipstream:deploy`Run a deploy (or `--deployment=`)`slipstream:rollback`Point `current` at the previous release`slipstream:healthcheck`Pre-swap checks (DB, Redis, custom)`slipstream:heartbeat`Refresh this node in the fleet registry, publish its `.env` snapshot, and apply any queued env push (scheduled every minute)`slipstream:prune`Trim old deploy events + webhook deliveries + terminal env pushes; fail stuck deploys/applies (scheduled daily)`slipstream:relay`Fan a stored relay payload out to peers (spawned detached; not run by hand)`slipstream:env-apply`Apply this node’s queued shared `.env` push (spawned detached by a nudge; also the manual escape hatch)`slipstream:env-nudge`Nudge peers to apply a queued env push (spawned detached; not run by hand)Manual escape hatch if the app is hard-down: run `php artisan slipstream:deploy` over SSH/console.

Multi-server notes
------------------

[](#multi-server-notes)

- All nodes share the **same application database** (`slipstream_*` tables).
- Only one migration-eligible server runs `migrate --force` per commit (cache lock). In a multi-server fleet this lock **must** live on a shared cache store (Redis, database, memcached) — with the `file`/`array` driver each node gets its own lock and all of them migrate. Pin it with `SLIPSTREAM_MIGRATION_LOCK_STORE` if your default store isn't shared.
- Pause a server to accept webhooks but skip builds.
- Notifications (mail, Slack, Discord, generic webhook) are configured under Settings.
- **Horizon and Reverb restarts are auto-detected** — a deploy only restarts them when the package is actually installed in the release. Force or disable per node with `SLIPSTREAM_RESTART_HORIZON` / `SLIPSTREAM_RESTART_REVERB` (`true`/`false`).
- Relay fan-out is signed with a domain-separated, timestamped HMAC (replay-protected) and dispatched in a detached process, so the git provider always gets a fast `200`.
- **Scoped `.env` updates.** The Environment editor can write this server only, selected servers, or the whole fleet. Cross-server pushes are queued in the shared DB (the source of truth) and applied by each node’s minute heartbeat; peers with a **Base URL** also get an instant, signed nudge (same HMAC scheme as relay, its own domain tag) so changes land in seconds. The nudge is content-free ("check your queue") — the heartbeat is the reliable backstop, so a missed nudge only costs latency.
- Each node publishes an **encrypted snapshot** of its `.env` to the shared DB (so the dashboard can view/edit any server and flag env drift). Because these snapshots and the existing encrypted settings ride `APP_KEY`, the fleet **must share one `APP_KEY`**.
- Fleet/multi-target pushes are a **full-file replace that preserves per-server keys** — `SLIPSTREAM_SERVER` and other per-node values keep each node’s own line (`SLIPSTREAM_ENV_PER_SERVER_KEYS`, comma-separated). Editing a single server writes exactly what you save (no merge).
- Turn the whole feature off with `SLIPSTREAM_ENV_SYNC=false`: no snapshots are stored, the queue/nudge/apply path is inert, the `/hooks/env-sync` route `404`s, and the editor collapses to a this-server-only tool.

Security notes
--------------

[](#security-notes)

- The hook endpoints (`/hooks/deploy`, `/hooks/relay`, `/hooks/env-sync`) are the only unauthenticated routes: HMAC/token verified, CSRF-exempt (so provider POSTs work out of the box), rate limited, and body-capped. No host-app CSRF configuration is required. The env-sync nudge carries no `.env` content — it is a signed, replay-window-bounded, domain- separated "check your queue"; the actual content travels only through the shared database.
- The dashboard gate is enforced on the initial page load **and** on every Livewire action (persistent middleware + an in-component check), so actions can't be driven from a stale page snapshot.
- Every state-changing dashboard action (deploy, rollback, setting/pipeline/hook/server change, `.env` edit) records the acting user in `slipstream_activity_log`.

Security
--------

[](#security)

- Dashboard is gated; the only unauthenticated package routes are the webhook + env-sync endpoints, verified by secret/HMAC.
- Sensitive settings, secrets, and per-node `.env` snapshots/pushes use Laravel encryption (`APP_KEY`).
- Prefer a non-root deploy user with scoped sudo for FPM/supervisor reloads — see `stubs/sudoers-slipstream`.
- Deploy logs (`{app_root}/deploy/logs/*.log`) are created `0600`: hook output is stored verbatim, in that file and in `slipstream_deployment_events`, and it routinely echoes secrets. Nothing redacts it.
- **Webhook deploys are idempotent per commit.** A provider HMAC signs the body and nothing else — no timestamp — and the delivery id used for de-duplication rides in an unsigned header, so a captured delivery could otherwise be replayed forever. A delivery naming the SHA this node already deployed successfully is ignored. Retrying a *failed* deploy with the provider's "redeliver" button still works, as does rolling forward after a rollback. Set `SLIPSTREAM_ALLOW_REDEPLOY_SAME_SHA=true` to opt out (the dashboard's Redeploy button does the same job).
- Branch and tag names are validated before they reach `git` (`Slipstream\Support\GitRef`). Every git call is array-form — no shell — so the concern is argument injection: git parses options after positional arguments, and a ref beginning with `-` would be read as one.

### Two secrets

[](#two-secrets)

Slipstream keeps the value you hand to your git host separate from the one your servers trust each other with:

KeyUsed forWho sees it**Webhook secret**GitHub HMAC, GitLab token, Bitbucket HMACpasted into your git host — GitLab transmits it verbatim in every `X-Gitlab-Token` header**Fleet key**relay fan-out + env-sync nudges between your own nodesnever leaves the fleet; each node reads it from the shared DBThey are separate because a relay push carries an arbitrary commit SHA and pipeline id — if one secret did both jobs, anyone who obtained the webhook secret at the provider (a git-host admin, a header-logging proxy, a leaked CI variable) could deploy any commit in your mirror on every node.

Fresh installs get both from `slipstream:install`. **Installs that predate the split keep using the webhook secret for peer traffic** so a fleet mid-upgrade doesn't break; separate them from Dashboard → **Webhook → Fleet key → Generate separate fleet key** once every node runs this version. Rotating the fleet key needs no provider changes — peers pick it up from the shared database immediately.

Preview the dashboard locally (no host app)
-------------------------------------------

[](#preview-the-dashboard-locally-no-host-app)

You can view the full dashboard without installing Slipstream into a project — it boots the package in a throwaway app via `orchestra/testbench` and seeds sample data:

```
composer install
composer preview:build   # publish assets + fresh migrate + seed sample data
composer preview         # serve at http://127.0.0.1:8000/slipstream
```

The gate is open automatically (the preview app runs in the `local` environment). The harness files — `testbench.yaml`, `preview/`, and `database/preview.sqlite` — are dev-only and never part of the installed package.

Contributing / tests
--------------------

[](#contributing--tests)

```
composer test
```

### Dashboard styling

[](#dashboard-styling)

The dashboard ships a **pre-compiled** `public/app.css` — host apps consume it as-is (via `slipstream:publish --assets`) and never need Node or a build step. The stylesheet is built with Tailwind CSS v4 at the **package** level; only maintainers editing the UI run it:

```
npm install
npm run build        # resources/css/app.css -> public/app.css (commit the result)
npm run dev          # watch mode while iterating
```

License
-------

[](#license)

MIT

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity35

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

1d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/135189110?v=4)[forthmediallc](/maintainers/forthmediallc)[@forthmediallc](https://github.com/forthmediallc)

---

Top Contributors

[![DevFloSupport](https://avatars.githubusercontent.com/u/183428775?v=4)](https://github.com/DevFloSupport "DevFloSupport (2 commits)")

---

Tags

laravelwebhookdeployenvoyerzero-downtime

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/slipstream-slipstream/health.svg)

```
[![Health](https://phpackages.com/badges/slipstream-slipstream/health.svg)](https://phpackages.com/packages/slipstream-slipstream)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M348](/packages/psalm-plugin-laravel)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M137](/packages/laravel-pulse)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M134](/packages/roots-acorn)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k30.2M151](/packages/laravel-cashier)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

77922.3M186](/packages/laravel-mcp)[tallstackui/tallstackui

TallStackUI is a powerful suite of Blade components that elevate your workflow of Livewire applications.

728176.2k14](/packages/tallstackui-tallstackui)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
