From a git push to a live, health-checked release
Every deploy builds off-box, swaps in a new container next to the old one, and only switches traffic once the new release proves healthy. Nothing customer-facing goes through a moment where the app is half-served.
Picking a build strategy
Sproobo builds strictly from your repo — no source ever gets hand-edited server-side:
- Dockerfile present → it's used. A monorepo Dockerfile that doesn't live at the repo root is still built from the repo root, with --dockerfile apps/web/Dockerfile pointing at it — so COPY instructions can still reach sibling workspace packages.
- No Dockerfile → Railpack. Railpack auto-detects the framework (Node, Next.js, and friends) at the repo root and builds without you writing a Dockerfile at all.
Either way the image is built in a capped, control-plane-managed builder — CPU, RAM, and disk limits keep a runaway build from ever threatening a customer server, because it was never running there.
Build-time environment variables
Your app's configured env is also made available during the build — needed for things like a Prisma generate step reading DATABASE_URL, or Next.js inlining NEXT_PUBLIC_* values into the client bundle. How it's delivered depends on the strategy, and the difference matters for secrets:
- Dockerfile strategy — each entry becomes a --build-arg KEY=VALUE. Build-args land in the image history, so this is not secret-safe — your own Dockerfile decides what each declared ARG does with the value.
- Railpack strategy — entries are delivered as BuildKit secret mounts instead, read from the build process's environment and never written into argv or image history.
Changing a build-time value and redeploying always takes effect on the next build — even at the same git commit. Sproobo detects the change and forces the affected build layers to re-run, so a value like NEXT_PUBLIC_API_URL can never get silently stuck on a stale, cached build.
The deploy pipeline
Every deploy — first ever or the thousandth — runs the same blue-green sequence:
Build off-box, push to the registry
The image is tagged to the git commit and pushed to Sproobo's S3-backed OCI registry. A Release row snapshots the image tag and resolved config.
The agent pulls the image
The server's agent pulls the new image with scoped registry auth.
New container starts alongside the old
The new release runs on the internal sproobo-int network while the old container keeps serving all live traffic, untouched.
Health gate
The new container is probed at --health-path (default /healthz; a static-image app has no app-level health endpoint, so it passes /) before it ever sees a real request.
Atomic proxy switch
Once healthy, the proxy route for the apex and www domains flips to the new container in one caddy validate-gated reload. The last-known-good config is retained.
Retire the old container
The previous container stops. Its image tag is kept in the registry — that's what makes rollback instant.
If the new container never turns healthy, the old one is still the one serving — the deploy is simply marked failed, with zero customer impact. The only case that auto-rolls-back is a health check that fails after the proxy already switched, and that rollback is automatic and immediate.
Releases & rollback
Every deploy is an immutable, image-tagged Release. Sproobo keeps the last N per app and garbage-collects older tags from both the registry and the box. New apps default to keeping 3 releases, capped by your plan — Free keeps 1, Pro up to 5, Team and Enterprise up to 10 — and the number is adjustable per app within your plan's cap.
Rolling back doesn't rebuild anything — it replays the same blue-green steps against an already-built image:
sproobo rollback <appId> -yWith no --to, the last known-healthy release is picked automatically. Target a specific one (ids come from sproobo deployments) instead:
sproobo rollback <appId> --to <releaseId> -yRollback is seconds, not minutes — there's no image to rebuild, just a new container start and a health-gated switch.
When a deploy fails, the logs come with it
If a deploy fails at or after the health gate, Sproobo automatically captures the failing container's own log tail and attaches it to the deployment — the probe error alone ("never healthy after N attempts") rarely says why; the container's own output usually does. You see it in the deploy view without SSHing into the box to go looking.
Concurrency: one deploy at a time, per app
Each app holds a deploy lock while a deploy is in flight. A second deploy triggered mid-flight queues or is rejected with a clear status rather than racing the first — and because releases are immutable and the swap is atomic, even a near-simultaneous deploy can't leave an app half-served.
Auto-deploy on push
Turn on auto-deploy for an app and a GitHub push to its configured branch builds and deploys automatically, no manual trigger needed. It requires a connected GitHub App installation to receive the webhook, and is editable any time from the app's settings.
Private repositories need that same GitHub App installation to clone at build time. Creating an app from the CLI (or via an AI agent) auto-links the org's installation whose account owns the repo — no separate linking step. In the dashboard's New app flow you pick the installation up front instead, and choose the repo from its list.
Creating an app
From the dashboard's New app flow, or the CLI:
sproobo apps create my-app --server <serverId> --repo https://github.com/you/app --domain example.com --dockerfile apps/web/Dockerfile -yPort defaults to 3000 and branch defaults to the repo's default branch. --dockerfile is only needed for a monorepo Dockerfile that isn't at the repo root; omit it and a repo with no root Dockerfile falls back to Railpack automatically.
Next
- Serving pre-built or static files instead of running a framework server? See Static sites.
- Walk the full enroll-to-first-deploy flow on Getting started.
- Install the CLI used above via the one-liner on Scripts & one-liners.