Skip to content

Core concepts

This page is the mental model behind everything else in the docs. Read it once and the rest of the reference will make sense. Every noun here — organization, project, deployment, database, function, cronjob, monitor — is a real resource with its own API, lifecycle, and status enum.

An organization is the tenant that owns everything: projects, databases, functions, cronjobs, monitors, team members, billing, and usage. Every resource is scoped to exactly one organization, and every API call resolves to one organization before it touches data.

  • psk_ API tokens are self-contained — they encode the user, the organization, and the role, so no organization needs to be named on the request.
  • JWTs (used by the dashboard) carry the user but not the org, so they must pass ?organization_id= or the x-organization-id header.

Access is role-based (owner, admin, member). Paid features and instance tiers are gated at the organization level: a create call checks whether the org has an active paid plan before it will let you choose a non-free tier or a paid-only feature — a raw API caller cannot sneak a free org onto a paid tier. See Authentication and Organizations & teams.

Card hard-wall. An organization that has never verified a payment card gets 402 on non-allowlisted routes until a card is verified. This is separate from plan limits.

A project is one deployable app. Its deploy type is stored on the slug column and is one of:

Deploy typeWhat it isHow it is served
staticA pre-built site or SPAPublished to the global CDN, served with no pod
containerA long-running serverA Kubernetes pod fronted by a Kong service
autoYou pick nothingClassified at create time, then persisted as static or container

Key facts about projects:

  • The name is the address. A project’s name becomes its public subdomain https://<name>.pandastack.app, so the name is globally unique across all organizations (not just within yours) and must be a valid DNS label: lowercase alphanumerics and hyphens, starting and ending alphanumeric, up to 63 characters.
  • Static projects build in a microVM (default build command npm run build) and publish to the CDN origin. An SPA gets an index.html fallback for unmatched routes. A static project connected to Git still runs a server-side build (install + postinstall); only a raw static upload runs no build at all and cannot be redeployed from a repo — you re-upload it.
  • Container projects run as a pod fronted by a Kong service. On the free tier a container uses scale-to-zero (one replica that sleeps when idle and cold-starts on the next request); paid tiers can enable HPA autoscaling, which is always-warm and mutually exclusive with scale-to-zero.
  • Auto-detect builds via the container path with Railpack so the first build always works, while a classifier decides whether the repo is really a pure static SPA (CDN, no pod) or needs a container. Classification is asymmetric: it returns static only on positive proof; on any doubt or failure it falls back to container (the safe default). See Auto-detect deploys.

Deployments have their own status enum: queued | running | succeeded | failed.

Creating or deploying a project writes the project/deployment row and a transactional outbox row (topic deploy.requested) in one database transaction. A relay publishes that message to a queue; a background worker consumes it and runs the build and rollout. This transactional-outbox pattern is why write calls return immediately while work continues asynchronously — and it is the same pattern used by databases (database.provision), cronjobs (cronjob.upsert), and other resources.

For container and auto builds, PandaStack uses Railpack buildpacks, which detect your framework and language version and build with no build or start command required. The language you pick on a container project controls how it is built:

languageHow it builds
autoRailpack buildpacks — zero-config framework + runtime detection
nodejsPlatform-generated Dockerfile (requires a start command)
pythonPlatform-generated Dockerfile (requires build and start commands)
goPlatform-generated Dockerfile (requires build and start commands)
dockerYour repository’s own Dockerfile

auto and static are exempt from build/start commands — Railpack and defaults figure them out. Before any build runs (for container, auto, and Git-based static), a source scan rejects malware and abuse kits. Full detail lives in Build & buildpacks.

All project traffic — static and container — is fronted by the PandaStack gateway. Every project carries a routing manifest that the gateway walks in order for each request:

  1. Redirects / rewrites (matched by rule type)
  2. Headers (applied as one global set-and-continue rule to all paths)
  3. Function dispatch (container manifests have a single catch-all route to the pod; static manifests have none)
  4. SPA fallback (for static SPAs)
  5. Implicit 404

The static filesystem step (the manifest’s static.target, pointing at the CDN origin) is tried before rewrites and before the SPA fallback. Redirect status codes mirror the serving plane: static redirects are 302, container redirects are 301. Because everything flows through the gateway, it is also where bandwidth accounting, analytics/metrics capture, and per-project rules are enforced. See Redirects, rewrites & headers and Custom domains.

Configuration reaches your app as two separate lists:

ListStored asReturned in responses?On the wire
env[{ name, value }]YesPlaintext
secretsBase64 of JSON([{ name, value }])NeverEncrypted (iv:cipher)

There is no separate build-time vs. runtime field in the schema — env flows to the build image and the running app the same way for every deploy type. The practical split is framework-driven: for a static / SPA build, only variables the framework exposes at build time reach the client bundle — for example NEXT_PUBLIC_* for Next.js. Runtime-only secrets are not baked into a static build. Reserved/system variables are guarded against override. See Environment variables & secrets.

Beyond projects, PandaStack exposes several resources, each with its own create/lifecycle API and status enum. Most are paid features (a free organization receives a 402 upgrade prompt).

ResourceWhat it isEngine / runtimePaid-only?Status enum
DatabaseManaged PostgreSQL / MySQLKubeBlocks (Cluster CR)Free tier: 1 per org; paid for tiers/HApending, running, failed, deploying, updating, deleting, queued, stopped
FunctionEdge function (code + zip)nodejs (index.js) / python (main.py)Yesversion-tracked; invocations logged
Scheduled functionAn existing function on a cronfn-invoker microVMRequires functions (paid)tracks lastRunAt / nextRunAt / lastStatus
CronjobScheduled container build+runContainer CronJobYespending, deploying, running, failed, deleting, deleted, updating, paused
MonitorURL uptime/health checkpg-boss schedulerYesrunning or pause (live UP/DOWN derived from last heartbeat)

A few load-bearing details:

  • Databases are reached through the *.db.pandastack.app proxy. Postgres clients route by SNI; MySQL clients send no SNI, so a public MySQL connection routes by a key encoded in the username (root.<dbname>). Connection URLs are returned masked (********) until a role-gated reveal. A free database is single-zone and auto-expires 30 days after creation (suspend at 30 days, purge 14 days later); paid databases never expire. See Databases.
  • Functions are name-based: the public invoke endpoint is POST /v1/functions/{name}/invoke, where {name} includes a random 6-character suffix minted at create time. Uploading new code increments a version; metadata-only edits do not. See Functions.
  • Cronjobs run either a build from a repo (github) or a public image directly (public_image, e.g. nginx:latest), on a 5-field cron schedule, with a full lifecycle (create, update, delete, pause, resume, run-now). See Cronjobs.
  • Monitors ping a URL on an interval and store heartbeats; the persisted row is only running or pause, while live health is derived from the latest heartbeat. See Uptime monitors.

PandaStack captures request data server-side at the gateway — there is no client SDK. Both metrics and analytics read from a ClickHouse event log, are tenant-safe (the project is ownership-checked in Postgres before any query), and fail open (if ClickHouse is unavailable the endpoints return an empty, disabled shape rather than an error).

MetricsAnalytics
AnswersPerformance & healthAudience
Fieldsrequests, errors, error rate, p50/p95/p99 latency, bandwidth, unique visitors, RPSunique visitors, views, returning visitors, top countries/referrers/pages, browsers, devices
GatingAvailablePaid feature and the project’s enable_analytics toggle
BotsCountedExcluded from audience counts

Both share the same time ranges — 1h, 24h, 7d, 30d — each with its own bucket width. See Metrics and Analytics.

Instance tiers (from free-tier up to m2-2xmemory-tier) set the CPU/RAM (and, for databases, disk) a workload gets; the same tier list and pricing applies to both container apps and databases. Plan limits (free / pro / premium / enterprise) set counts and monthly allowances and unlock paid-only features. Usage is metered on billable dimensions and billed for paid plans only; free plans never pay usage. See Plans & limits, Instance tiers, and Usage & metering.