---
url: /internals/deployment-tooling.md
---
# Deployment tooling internals

Evolve deploys itself: `php artisan deploy` rsyncs a release to the server, the `evolve:env:*` commands declare and verify environments, and Envoy tasks move data. The user side is on [Deployment](/getting-started/deployment) and [Environments](/getting-started/environments).

## Declarations

`core/config/evolve.php` → `deployment`: `default` (the environment `deploy` uses without an argument), `environments` keyed by name with `type` (`live`, `staging`, `testing` read as staging, `development`), `target` (directory under `server.path`), optional `url` and `php-bin`; `keep-releases`; `server` (user, host, path, php-bin); `hooks.before` (local) and `hooks.after` (remote). The legacy `target` and `allowed-targets` keys still work when `environments` is empty.

`core/src/Deployment/`:

| Class | Role |
|---|---|
| `Environments` (singleton) | parses the declarations into `DeploymentEnvironment` objects |
| `DeploymentEnvironment` | one environment: name, type, target, url, php binary, remote paths |
| `EnvironmentType` (enum) | the policy per type: `requiresNamedConfirmation()`, `allowsForcedDeploy()`, `warnsOnNonDefaultBranch()`, `acceptsDatabasePush()`, `allowsDebug()`, `debugsByDefault()`, `cachesPagesByDefault()`, `mustProtect()`, `redirectsMail()`, `showsBanner()`, `emitsTracking()`, `appEnv()` |
| `RemoteShell`, `RemoteShellFactory` | SSH command execution with one connection per command batch |
| `ServerEnv`, `EnvTemplate` | reading, masking and generating the server `.env` from `core/src/Console/Commands/stubs/server-env.stub` |
| `SharedStorage` | `shared/storage/app` creation, migration of release uploads into it, linking (2.12) |

`Evolve::environmentType()` resolves the running instance's type from `EVOLVE_ENV_TYPE`, falling back to `APP_ENV`. Everything posture-related (mail redirect, preview protection, banner, noindex, tracking emission, debug) asks the enum.

## The deploy command

`core/src/Console/Commands/Deploy.php` (`deploy {environment?} {--dry-run} {--keep-releases} {--rollback} {--file=} {--force} {--keep-caches}`; `--target` is deprecated in favour of the environment argument, `--force` is ignored on live): resolves the environment, applies the policy (typed confirmation for live, branch warning), runs `hooks.before`, probes the remote (a failed probe aborts rather than building a release without uploads, 2.11), creates the new release directory as a copy of the live one, rsyncs the working copy with `core/bin/deploy_exclude.txt` (publishable as `evolve.deploy-exclude`), links shared storage, runs migrations and the cache rebuild remotely, clears the page cache of the new release, runs `hooks.after`, switches the `current` symlink, purges old releases beyond `keep-releases`, and notifies Slack (`services.slack.webhook-url`) with `Notifications\NewReleaseDeployed` or `ReleaseRolledBack`. It refuses to sync a stale Livewire 2 bundle (2.11).

```mermaid
flowchart TD
    A["php artisan deploy, environment argument"] --> B["resolve the environment,<br/>apply the EnvironmentType policy:<br/>typed confirmation on live, branch warning"]
    B --> C["hooks.before, local"]
    C --> D{"probe the remote"}
    D -->|fails| X["abort, no release"]
    D -->|ok| E["new release directory<br/>as a copy of the live one"]
    E --> F["rsync the working copy<br/>minus deploy_exclude.txt"]
    F --> G["link shared/storage/app"]
    G --> H["remote: migrate, rebuild the caches,<br/>clear the page cache of the new release"]
    H --> I["hooks.after, remote"]
    I --> J["switch the current symlink"]
    J --> K["purge releases beyond keep-releases"]
    K --> L["Slack: NewReleaseDeployed"]
```

## Environment commands

`evolve:env:list`, `evolve:env:setup` (remote directories, server `.env` from the template, database, media), `evolve:env:show` (masked remote `.env`), `evolve:env:diff` (keys across example, local and remote), `evolve:env:verify {environment?} {--all}` (`APP_ENV` and `EVOLVE_ENV_TYPE` against the declaration, debug off on live, Sentry DSN on live, `APP_URL` and whether the declared URL answers, key drift against `.env.example`, a stale published Livewire bundle), `evolve:db:copy` (with sanitize hook, confirmation by type, settings cache forgotten on the receiving side), `evolve:logs`, `evolve:releases`, `evolve:artisan` (allowlisted read-only commands), `evolve:remote:cat`, `evolve:remote:status` (health in one round trip), `evolve:ssh`. All under `core/src/Console/Commands/`.

## Envoy

`core/bin/Envoy.blade.php`, imported by the project's `Envoy.blade.php`: `media:down`, `media:up`, `database:down`, `database:up`, `env:down`, `env:up`, `server:env`, `server:migrate`, `server:key`, `server:clean`, `server:diff`. Destructive tasks (`database:up`, `media:up`, `env:up`) require typing the target name and write timestamped safety backups first (2.5); database pushes never go into live (`acceptsDatabasePush()`).

## Health hooks

`evolve:env:verify` and the environment consistency health check both compare `APP_ENV` with `EVOLVE_ENV_TYPE`; the canonical host check fails when the page cache is on and `APP_URL` is not a real host.
