Apps & deploys

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:

01

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.

02

The agent pulls the image

The server's agent pulls the new image with scoped registry auth.

03

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.

04

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.

05

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.

06

Retire the old container

The previous container stops. Its image tag is kept in the registry — that's what makes rollback instant.

A failed health check never takes down the live app.

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> -y

With 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> -y

Rollback 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 -y

Port 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