Nani
In developmentZero-downtime, cloud-agnostic deployment for Laravel — one binary
The problem
Laravel deployment is fragmented and rented: Forge for provisioning, Envoyer for
zero-downtime deploys, Vapor for serverless — three separate paid SaaS products, each
opinionated about your cloud. Nani collapses all three into one static Go binary
driven by a single nani.yaml. It deploys to any cloud — AWS, GCP, DigitalOcean,
bare-metal — or to serverless (Lambda/Bref, Cloud Run), with no per-server
subscription and no vendor lock-in.
The interesting part — deploys as a saga
A deploy is an ordered list of small Steps sharing typed state. Steps that mutate
remote state implement a Compensator; when a later step fails, the runner walks
the completed steps backward and compensates — so an atomic symlink swap auto-rolls
back only the instances that already swapped. Side-effects (Slack/Sentry/CI
callbacks) are Observers that never gate the pipeline. It’s a
compensating-transaction pattern applied to infrastructure, which is what makes
partial-fleet failures safe.
Engineering highlights
- Driver-interface everywhere — storage, locks, secrets, fleet discovery, serverless, notifications: one small interface + a file per backend, selected by a YAML string. This is what makes it genuinely cloud-agnostic and trivial to extend.
- One feature core, four frontends — a CLI, a Bubble Tea terminal dashboard, an embedded SvelteKit web dashboard with live SSE log streaming, and a native macOS app (Wails) all call the same orchestrators. Zero duplication of deploy logic.
- Declarative failure policy — every step’s severity (
fatal | warn | skip) is config-driven and overridable per environment, replacing silent error-swallowing. - Distributed deploy lock (S3/file, TTL) and property-based tests on the risky invariants: env-override merging, semver parsing, atomic-swap command generation.
20+ self-registering provisioning recipes over streaming SSH; preview environments; maintenance mode; release rollback. MIT-licensed.