Scaling

Each app has three knobs:

Knob Default Notes
Replicas 1 Number of containers nginx load-balances over.
CPU millis 500 1000m = 1 CPU. Hard cap.
Memory MB 256 Hard cap.

These are stored on the App row as defaults, snapshotted into a Release each time you deploy, and reconciled by the Orchestrator.

Changing them

Dashboard: not in v1 UI yet — set via API:

curl -X PATCH https://paas.example.com/api/orgs/acme/apps/web/scale \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"replicas":3,"cpuMillis":1000,"memoryMb":512}'

The change marks the App proxy-dirty. On the next reconciliation tick the Orchestrator brings the desired replica count up (or down), and the ProxyController rewrites the upstream block.

How rolling updates work

When a new Release is promoted (after a successful build):

  1. Orchestrator computes the desired set: replicas containers of the new Release.
  2. It starts new containers in parallel (one per available replica slot) and waits for each one to pass its health probe.
  3. As soon as any new container is healthy, it begins draining old containers — one per loop tick (every LoopIntervalSeconds, default 5s) to keep churn bounded.
  4. ProxyController re-renders the upstream block on every change, only reloading nginx if nginx -t succeeds and the rendered config changed.

Why nginx and not a smarter LB?

For single-host with horizontal replicas, nginx's upstream { server …; server …; } round-robin is fine. The Orchestrator only adds a server to the block once it's healthy, and nginx's max_fails/fail_timeout will stop sending traffic to a member that crashes between health probes. Replace this with HAProxy / Envoy / Traefik when you have multi-region affinity needs.

Limits

  • Replicas are capped only by your host's RAM/CPU. Paas does not enforce cluster-level quotas in v1.
  • MemoryMb < 32 and CpuMillis < 50 are rejected at the API.
  • A replica that fails its health probe is left as Unhealthy and never enters the upstream pool. The reconciler will not "heal" it by replacing the container in v1 — you have to redeploy.

"Zero downtime" caveats

Rolling updates assume your app:

  • Returns < 500 to GET / quickly (so Healthy flips on the first probe).
  • Tolerates running side-by-side with the old version for one tick.
  • Doesn't take long-lived connections that span the swap (your old replicas get a SIGTERM with a 10s grace period before SIGKILL).

For workers / migrations / one-shots that don't fit this model: deploy them as a separate app (web-migrate) with replicas=0 and trigger them from your app's startup with a feature-flag gate. v1.1 will add explicit "release" / "pre-deploy" hooks.