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).
- 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. - A background loop reconciles every running app on a 30-second tick. An
app whose last request is older than
idle_timeout_secondsis put to sleep, so the real sleep moment is up to 30 seconds after the timeout expires. - Sleeping deletes the app's sandbox entirely — CPU, RAM, and disk are all
released. The app row flips to
status: "hibernated"andsandbox_idbecomes empty. What survives is the immutable artifact baked at the end of the last successful deploy, stored in object storage. - 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}:
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.
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:
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.
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}'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:
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 cheap200and anX-Pandastack-App: asleepheader without booting, and a running app does not have its idle timer reset by them. HEADrequests and browser chrome paths (/favicon.ico,/robots.txt,/sitemap.xml,/apple-touch-icon.png) neither warm nor wake.OPTIONSpreflights, 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
200from a sleeping app means "this app exists and is deployed", not "your process is running". If you need to assert the process is running, useauto_hibernate: falseand 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: falseinstead, 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:
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}'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)import { Client } from "@pandastack/sdk";
const client = new Client();
await client.databases.update(dbId, { always_on: true });pandastack database update <db-id> --always-on
pandastack database update <db-id> --no-always-onPATCH /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.
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
| Workload | Setting | Why |
|---|---|---|
| PR preview app | auto_hibernate: true, idle_timeout_seconds: 120–300 | Looked at in short bursts; a wake between reviews is unnoticeable. |
| Internal tool, demo, side project | Defaults (true, 900) | Idle most of the day; a wake on the first morning visit is fine. |
| Public marketing or docs site | auto_hibernate: true, longer idle_timeout_seconds | Real visitors keep it warm; crawlers do not, so it still sleeps overnight. |
| Payment or auth webhook receiver | auto_hibernate: false | The caller's timeout is tighter than a wake. |
| API another system polls on an SLA | auto_hibernate: false | Polls on health paths would not keep it warm anyway. |
| Database behind a pooled web app | Default auto-suspend | The pooler's open connections keep it awake while traffic flows. |
| Database behind a latency-sensitive API with no pooler | always_on: true | Serverless-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
502on 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
402and a paused app's URL serves a503page. 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
503with aRetry-Afterheader. 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
PATCHthat changesstart_command,port,env,root_directory,framework,install_command, orbuild_commandinvalidates 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.
-
502on 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.
Database operations: clone, restore, failover, and credentials
Run day-two operations on a managed PostgreSQL database — clone it, restore it to a point in time, resize it, rotate its credentials, wake it, and fail it over to a healthy host.
Execute Python in an isolated microVM via one API call
How to execute Python in an isolated microVM with one API call — create a Firecracker sandbox, run code, and read stdout from the Python or TypeScript SDK or raw curl.