How it works¶
What versaDeploy does under the hood, as diagrams. Every step is detailed in the reference.
What versa deploy does¶
The heavy lifting happens on your machine (or in CI); the server only receives the result and switches to it.
flowchart TD
A[versa deploy production] --> B{Anything changed since<br>the last deploy?<br><small>SHA256 of every file</small>}
B -- No --> Z0[Nothing to do]
B -- Yes --> C[Build on your machine<br><small>composer · npm · go build</small>]
C --> D[Compress and upload over SFTP<br><small>only what changed, in parallel</small>]
D --> E[Create releases/NEW on the server<br><small>reuses vendor, node_modules, .venv</small>]
E --> F[Link shared/<br><small>.env, storage, uploads…</small>]
F --> G[pre_deploy_server hooks]
G --> H[Switch current → releases/NEW<br><small>one symlink: zero downtime</small>]
H --> I[services_reload<br><small>PHP-FPM, nginx</small>]
I --> J[post_deploy hooks<br><small>migrations, caches</small>]
J --> K[Restart services<br><small>Go, Python, Node</small>]
K --> L{Health check}
L -- OK --> M[✅ Record the deploy<br>clean old releases<br>notify Slack/Discord/Telegram]
J -- fails --> R
K -- fails --> R
L -- fails --> R[↩️ Automatic rollback<br><small>current goes back to the previous release</small>]
R --> N[❌ Failed deploy: kept in the history<br>with its log, and notified]
First deploy to a new server¶
flowchart LR
A[New VPS<br><small>root over SSH</small>] --> B[deploy.yml<br><small>from examples/</small>]
B --> C[versa → Services → I<br><small>installs the stack, creates the<br>deploy user and the database</small>]
C --> D[versa deploy production<br>--initial-deploy]
D --> E[versa → Shared → .env<br><small>production settings</small>]
E --> F[versa deploy production<br>--force<br><small>migrations and caches</small>]
F --> G[🚀 App online]
Step by step in Getting started, and on video in Videos.
Every day¶
sequenceDiagram
actor Dev as You
participant Git
participant V as versa
participant S as Server
Dev->>Git: git commit
Dev->>V: versa deploy production
V->>V: detects changes and builds
V->>S: uploads only what changed
V->>S: switches to the new release
S-->>V: health check OK
V-->>Dev: ✅ done (and recorded in versa history)
Note over Dev,S: Something wrong in production?
Dev->>V: versa rollback production
V->>S: current → the previous release (1 second)
Where things live on the server¶
flowchart LR
current["current<br><small>symlink</small>"] --> R3
subgraph releases["releases/"]
R1["20261101-090000/app"]
R2["20261105-153000/app"]
R3["20261109-120000/app<br><small>the live release</small>"]
end
subgraph shared["shared/ (survives every deploy)"]
ENV[".env"]
ST["storage/"]
end
R1 -. ".env, storage" .-> shared
R2 -. ".env, storage" .-> shared
R3 -. ".env, storage" .-> shared
- Your production
.envlives only inshared/: it never goes through git or your machine. The first deploy creates it from.env.example; you edit it in the TUI's Shared view. - Every release links the same
.env; rolling back doesn't change it. - The last
releases_to_keepreleases (5 by default) are kept: those are the ones a rollback can go back to.
Rollback¶
flowchart LR
subgraph Before
C1[current] --> V3a[v3 ❌]
end
subgraph After
C2[current] --> V2b[v2 ✅]
end
Before -- "versa rollback production" --> After
Nothing is built or uploaded again: the previous release is still on the server, and versa only moves the link, reloads PHP-FPM/nginx and restarts the services. The same happens by itself when a deploy fails after switching.
Staging, production and CI¶
flowchart LR
P[git push to main] --> S[GitHub Actions<br>versa deploy staging]
T[git tag v1.2.0<br>git push --tags] --> PR[GitHub Actions<br>versa deploy production]
PR --> TG{Does the commit have<br>a v* tag?}
TG -- Yes --> OK[✅ Deploy]
TG -- No --> NO[⛔ Refused<br><small>deploy_tags: v*</small>]
Each environment has its own server (or its own remote_path) and its own .env. With deploy_tags: "v*", production only accepts tagged versions, even from someone's laptop. The example workflow is examples/github-actions.yml.
The Services view (provisioning)¶
flowchart TD
A[versa → Services] --> B[Detects the server<br><small>distro, what's installed, versions</small>]
B --> C[Ticks what your app needs<br><small>from deploy.yml and the repo</small>]
C --> D{Does it clash with<br>something installed?}
D -- Yes --> X[⛔ Not installed<br><small>never nginx over Apache,<br>never a second PHP</small>]
D -- No --> E[I: installs over SSH as root<br><small>no agent</small>]
E --> F[What's installed is left alone<br><small>no config changes, no restarts</small>]
Details in Server services.