PandaStack

Control scale-to-zero and cold starts

Tune or disable idle sleep for apps and managed databases, and decide when a warm app is worth more than the idle cost it saves.

PandaStack apps and managed databases both stop consuming compute when nothing is using them, and come back on the next real request or connection. That trade is deliberate: you pay nothing for idle time, and the first request after an idle stretch waits for a wake. This guide shows how to move that dial in either direction, per app and per database.

How app scale-to-zero works

An app has two settings that govern it: auto_hibernate (default true) and idle_timeout_seconds (default 900 — fifteen minutes, minimum 60).

  1. Every request that reaches your app through its URL bumps the app's last_request_at. The write is throttled to at most once per minute per app, so traffic volume never matters — only recency.
  2. A background loop reconciles every running app on a 30-second tick. An app whose last request is older than idle_timeout_seconds is put to sleep, so the real sleep moment is up to 30 seconds after the timeout expires.
  3. Sleeping deletes the app's sandbox entirely — CPU, RAM, and disk are all released. The app row flips to status: "hibernated" and sandbox_id becomes empty. What survives is the immutable artifact baked at the end of the last successful deploy, stored in object storage.
  4. The next real request wakes the app: PandaStack boots a fresh sandbox from that artifact on whichever host has capacity, waits for your app to bind its port, then forwards the request. Concurrent requests during a wake are coalesced into one boot.

Because the sandbox is deleted, an app's local disk is not durable across a sleep/wake cycle. Anything your app wrote at runtime — uploads under /app, a SQLite file, a cache directory — is gone after it sleeps. The woken sandbox is a fresh copy of the deploy-time artifact. Put anything that must survive in a managed database or external object storage.

An app is never slept while it has a live proxied connection (a WebSocket, an SSE stream, or a long download in flight), and it is never slept before its first successful deploy has produced a wakeable artifact.

Read the current settings

Both fields are returned by GET /v1/apps/{id}:

check-idle-config.sh
curl -s https://api.pandastack.ai/v1/apps/$APP_ID \
  -H "Authorization: Bearer $PANDASTACK_API_KEY" \
| jq '{status, auto_hibernate, idle_timeout_seconds, last_request_at, sandbox_id}'

A sleeping app reads back as:

{
  "status": "hibernated",
  "auto_hibernate": true,
  "idle_timeout_seconds": 900,
  "last_request_at": "2026-08-17T09:12:44Z",
  "sandbox_id": ""
}

Change the idle window

PATCH /v1/apps/{id} accepts idle_timeout_seconds. The minimum is 60; a smaller value is rejected with 400 and the message idle_timeout_seconds must be >= 60.

sleep-after-2-minutes.sh
curl -X PATCH https://api.pandastack.ai/v1/apps/$APP_ID \
  -H "Authorization: Bearer $PANDASTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"idle_timeout_seconds": 120}'

The Python SDK passes arbitrary app fields through to the same endpoint:

sleep_after_2_minutes.py
from pandastack import Client

client = Client()
client.apps.update(app_id, idle_timeout_seconds=120)

Short windows suit per-PR preview apps and demos that are looked at in bursts. Long windows suit an app with sporadic but latency-sensitive traffic — a webhook receiver that fires a few times an hour, for example.

Turn scale-to-zero off

Set auto_hibernate: false on an app that must answer the first request as fast as the thousandth: a payment webhook endpoint, a health-critical internal service, an API another system polls on a tight timeout.

keep-app-warm.sh
curl -X PATCH https://api.pandastack.ai/v1/apps/$APP_ID \
  -H "Authorization: Bearer $PANDASTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"auto_hibernate": false}'
keep_app_warm.py
from pandastack import Client

client = Client()
client.apps.update(app_id, auto_hibernate=False)

You can also set both fields when the app is created:

create-always-on-app.sh
curl -X POST https://api.pandastack.ai/v1/apps \
  -H "Authorization: Bearer $PANDASTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "billing-webhooks",
        "git_url": "https://github.com/acme/billing-webhooks",
        "git_branch": "main",
        "auto_hibernate": false
      }'

An app with auto_hibernate: false holds its sandbox continuously and bills continuously. Every app runs at a fixed 4 GiB / 8 vCPU regardless of what you pass for cpu or memory_mb, so an always-on app is a full 4 GiB of committed fleet memory for as long as it exists.

Turning scale-to-zero off does not make an app immortal. If its sandbox is lost (host replacement, a crash the health monitor can't restart), a request to the app triggers a redeploy from Git rather than a wake — which takes as long as a normal build.

Automated traffic will not defeat scale-to-zero

Pointing an uptime monitor at an app to keep it warm does not work, and this is intentional. Inbound requests are classified before they touch the app:

  • Named uptime monitors (UptimeRobot, Pingdom, StatusCake, Better Uptime, Checkly, Cronitor, Uptime Kuma, updown.io, Site24x7, Datadog, New Relic, Prometheus/blackbox, kube-probe, and similar) are recognised by User-Agent on any path or method. A sleeping app answers them with a cheap 200 and an X-Pandastack-App: asleep header without booting, and a running app does not have its idle timer reset by them.
  • HEAD requests and browser chrome paths (/favicon.ico, /robots.txt, /sitemap.xml, /apple-touch-icon.png) neither warm nor wake.
  • OPTIONS preflights, conventional health paths (/health, /healthz, /healthcheck, /livez, /readyz, /ping, /status, /up, /_health, /_healthz), /.well-known/*, search and social crawlers, and requests with an empty User-Agent do wake a sleeping app and are served real content — but they never reset the idle timer.
  • Everything else is treated as real user traffic: it wakes the app and keeps it warm.

The practical consequences:

  • Your monitor's dashboard stays green while the app sleeps. A 200 from a sleeping app means "this app exists and is deployed", not "your process is running". If you need to assert the process is running, use auto_hibernate: false and check the app's own endpoint from a client that is not a recognised monitor.
  • A public custom domain attracts constant crawler and scanner traffic. That traffic no longer pins the app awake, so an app on a custom domain sleeps on the same schedule as one on its default URL.
  • A synthetic cron that curls your app on a timer does count as real traffic if it sends an ordinary User-Agent and hits a non-health path. That is not a supported way to keep an app warm — use auto_hibernate: false instead, which is cheaper to reason about and does not burn a wake on every poll.

Managed databases: auto-suspend and always_on

A managed PostgreSQL database follows the same idea with a different signal and a different durability story. The database's data volume is durable — only the compute is suspended, so nothing is lost across a suspend/resume cycle.

  • The idle signal is PostgreSQL itself, not HTTP traffic. A database counts as active while any non-platform client backend is connected — including one that is merely open and idle — or while a base backup is streaming. PandaStack's own health probe and dashboard stats connections are excluded by application_name, so platform traffic never keeps a database awake.
  • Because an open connection counts, a connection pooler that holds a warm pool keeps the database awake indefinitely. The idle countdown only starts after the last client connection closes.
  • After the idle window (currently 15 minutes) with no client activity, the database VM is snapshotted and stopped. Compute billing stops; storage continues.
  • The next connection — native postgres:// or the REST query broker — resumes it transparently. You see a slower first query, not an error.

There is no per-database idle-window setting. The control is always_on, which opts a database out of auto-suspend entirely:

db-always-on.sh
curl -X PATCH https://api.pandastack.ai/v1/databases/$DB_ID \
  -H "Authorization: Bearer $PANDASTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"always_on": true}'
db_always_on.py
from pandastack import Client

client = Client()

# At creation:
db = client.databases.create(label="prod", always_on=True)

# Or later:
client.databases.update(db["id"], always_on=True)
db-always-on.ts
import { Client } from "@pandastack/sdk";

const client = new Client();
await client.databases.update(dbId, { always_on: true });
db-always-on-cli.sh
pandastack database update <db-id> --always-on
pandastack database update <db-id> --no-always-on

PATCH /v1/databases/{id} accepts only always_on; any other body returns 400 with no updatable fields provided (supported: always_on).

To resume a suspended database without opening a client connection — useful in a deploy script that wants the database warm before traffic arrives — call the explicit wake endpoint. It returns 202; poll GET /v1/databases/{id} until status is running.

db-wake.sh
curl -X POST https://api.pandastack.ai/v1/databases/$DB_ID/wake \
  -H "Authorization: Bearer $PANDASTACK_API_KEY"

Apps have no equivalent wake endpoint. A real request to the app's URL is the only way to wake it, which is why a warm-up script must send ordinary traffic to a real path — a HEAD or a /healthz probe will be answered without waking anything.

Choosing between cost and first-request latency

WorkloadSettingWhy
PR preview appauto_hibernate: true, idle_timeout_seconds: 120300Looked at in short bursts; a wake between reviews is unnoticeable.
Internal tool, demo, side projectDefaults (true, 900)Idle most of the day; a wake on the first morning visit is fine.
Public marketing or docs siteauto_hibernate: true, longer idle_timeout_secondsReal visitors keep it warm; crawlers do not, so it still sleeps overnight.
Payment or auth webhook receiverauto_hibernate: falseThe caller's timeout is tighter than a wake.
API another system polls on an SLAauto_hibernate: falsePolls on health paths would not keep it warm anyway.
Database behind a pooled web appDefault auto-suspendThe pooler's open connections keep it awake while traffic flows.
Database behind a latency-sensitive API with no pooleralways_on: trueServerless-style connect-per-request would pay a resume on the first call after each idle stretch.

Two things to weigh beyond latency:

  • Fleet capacity. Every always-on app holds 4 GiB of committed memory. Capacity on PandaStack is bounded by fleet memory, so a workspace full of always-on apps is more likely to hit a 502 on a new create.
  • Free tier. The free tier is a $5.40/month credit. Always-on resources consume it continuously; scale-to-zero resources consume it only while in use. When the credit is exhausted, compute stops and the workspace is paused — creates return 402 and a paused app's URL serves a 503 page. Nothing is ever auto-deleted, and upgrading lifts the pause on the next reconcile (about a minute).

What can go wrong

  • The first request after sleep is slow, or returns 503 with a Retry-After header. That is the wake in progress. The response is a JSON body ({"error": "app is waking up", ...}) for API clients and a small HTML page for browser navigations. Retry after the interval it names.

  • A wake takes far longer than usual. If the deploy-time artifact is unusable, PandaStack falls back to redeploying the app from Git so the URL never dead-ends. Check GET /v1/apps/{id}/deploys — a deployment created at the moment of the wake means it took the redeploy path.

  • An app started redeploying instead of waking after a config change. A PATCH that changes start_command, port, env, root_directory, framework, install_command, or build_command invalidates the baked artifact, because it no longer matches the config. The next wake rebuilds from Git. Trigger a deploy yourself after such a change if you want the fast wake path back before real traffic arrives.

  • An app never sleeps despite auto_hibernate: true. Check for a live WebSocket or SSE connection — an app with one open is skipped by the idle sweep. Also check that the app has completed a successful deploy; one that never has is left running because there is nothing to wake it from.

  • Files written at runtime disappeared. Expected: the sandbox is deleted on sleep. See Files my app wrote have disappeared.

  • The app URL serves "This app is paused". The workspace is out of free credit; no request wakes a suspended app. See 402 Payment Required.

  • 502 on creating a new app or database. Fleet memory pressure. See 502 Bad Gateway on create.

Everything else lives in Troubleshooting. For the wider lifecycle model, see Apps, Databases, and Ephemeral environments.

On this page