snoozestackdocs

The runtime

Everything your project's backend does — running functions, holding the database, storing files, working queues, firing schedules, recording signals, serving sites, verifying who's calling — is one self-contained service: the snoozestack runtime. snoozestack dev runs it on your machine as a single native process, and snoozestack publish runs the same thing on managed hosts. There is no separate database server, auth container, or job worker to assemble; the runtime is the whole anatomy.

Why one service

A conventional backend is a committee: a database here, an auth service there, a job runner, an object store, and a pile of configuration wiring them together — which is why “works on my machine” and “works in production” are usually two different claims. Making the whole backend one process makes the two claims the same one: what your laptop runs and what the hosted fleet runs are the same code path, differing only in configuration.

It also makes the project portable. The runtime reads your committed snoozestack/ folder and keeps all of its state under one directory — so moving, replicating, or throwing away a backend is moving, replicating, or throwing away a folder, not an infrastructure migration.

The layers, bottom up#

Data. The base of the stack is what the runtime owns on disk: a database built from your committed schema, one directory per declared storage bucket, and the signals store. All of it lives under .snoozestack/ — locally that's a folder you can delete to start fresh (snoozestack dev reset); hosted it's a volume that is continuously replicated to durable storage.

Functions. Above the state sits the only code that can touch it: functions you commit, on one contract — export default (req, capabilities). Every entry point lands here: an HTTP call from your app, a queue message, a cron schedule, a webhook, an MCP tool call. There is no client-facing route to the database at all.

Capabilities. The seam between those two layers is explicit: a function declares in project.toml which capabilities it may use — db, storage, auth, signals, push, secrets — and receives exactly those as its second argument. An undeclared capability isn't forbidden at runtime; it simply isn't there. See Permissions.

Identity. Hosted projects serve magic-link, Google, Apple and password sign-in from their runtime, with users, sessions and signing keys in a separate, replicated auth.db. Verification checks the local signature and session row, so revocation is immediate. The portal relays outbound email. snoozestack dev runs the same plane from .snoozestack/auth.db, seeded with a test account, with a local account chooser standing in for Google and Apple and an outbox standing in for email. See Authentication.

App. The surface your users actually meet. Declared sites are served by the same process locally, next to the functions they call, and publish as immutable versions hosted; a custom domain fronts the same content; the SDKs your app is built with call in; and what happens out there comes back as Signals.

Agents. Because the project is files and the operations are explicit commands, the whole lifecycle is drivable by more than hands: the CLI for you and CI, the management API for scripts, and MCP for AI clients — all reading and writing the same project model, never a hidden setting.

One process, three places#

WhereHow it startsWhat's different
snoozestack devIn-process, native — no DockerWatches your files, serves the local dashboard, seeds fresh databases and the user@test.com/1234 account, and simulates the provider boundary (Google/Apple chooser, email and push outboxes)
DockerInternal deployment detailThe hosted service runs each project in an isolated runtime container
Hostedsnoozestack publishOne container per project and namespace; state replicated continuously; watcher, dashboard, seed and the seeded account off; real providers, SES and APNs at the boundary

The differences are configuration, not forks — hosted mode is the same engine with the development conveniences turned off and durability turned on. That's the property the rest of the docs lean on: if it worked under snoozestack dev, publishing doesn't change what runs, only where.

What runs locally, service by service#

Every service the runtime offers is one of four things under snoozestack dev: the same code as hosted; the same code down to a named boundary and a local simulation below it (a local account chooser instead of Google, an outbox instead of SES or APNs, a SQLite queue with SQS's shape instead of SQS); or a surface that exists in one mode only, with the reason. This table is generated from the runtime's own parity registry, whose test fails when a route or capability is left undeclared — so it is the code's claim, not the docs'.

SurfaceLocallyNotes
/Local onlyThe local dashboard at http://localhost:8787; hosted the portal is the console. Hosted, the console is the portal; the runtime's / is a text index and the browser page is never served on a project host.
/_dashboard/*Simulated below a boundarySame JSON routes and the same identity plane behind them; hosted they are gated by the portal's per-app token, locally by loopback and a per-run write token. The Emails and Notifications outbox views exist locally only.
/_storage/upload/*Simulated below a boundarycapabilities.storage.signedUploadUrl() returns a URL the browser PUTs to in both modes; hosted it is a presigned S3 URL, locally the dev server verifies the signature and writes the file itself.
/_storage/*Simulated below a boundarySame route; hosted it redirects to a portal-signed S3 URL, locally it serves the file from .snoozestack/storage with a locally signed URL that survives a restart.
/storage/v1/object/public/*Same codeAccepted in both modes for URLs stored before the runtime served storage; resolves to /_storage.
/signals/v1/eventsSame codesignals-http.js in both modes. The anon key is checked upstream by the project host hosted and by the dev server locally; the ingestion quota is hosted-only (see below).
/signals/v1/querySame codesignals-http.js in both modes, same key rule as ingest.
/_queues/*Simulated below a boundaryHosted a queue is SQS, drained on a 10-second tick with a visibility timeout, receive counts and a per-queue dead-letter queue. Locally dev-queue.js reproduces that shape in .snoozestack/queues.db (async by default; [dev] queues = "immediate" for tests).
/_webhooks/*Simulated below a boundaryHosted an API Gateway URL lands the body in SQS; locally POST /_webhooks/<name> enqueues into the same local queue and answers 202 the same way.
/_sites/*Local onlyLocally each declared site also gets its own port serving it at the root, with /api proxied to the runtime. Hosted, sites upload to S3 and are served by CloudFront on the project host; the runtime never serves site files there.
/api/*Local onlyOnly the origin changes between a hosted URL and a local one. Hosted the project host strips /api before a request reaches the runtime; locally the dev server strips it itself so a URL written for https://<ref>.snzzz.com/api/… works with only the origin swapped.
/mcp/v1/*Local onlyAn MCP client configured with the production URL shape can be pointed at localhost. Hosted the portal maps /mcp/v1/<name> onto the function; locally the dev server does, for mcp = true functions only.
/functions/v1/*Same codeThe SDK's URL shape, answered by the runtime in both modes.
/<function>Same codeWhat the portal dispatches hosted; the same route locally.
/auth/*Simulated below a boundaryhosted-identity.js in both modes — users, passwords, magic links, OAuth state, sessions, rate limits, self-delete — on auth.db. Locally the provider is a local account chooser (Google/Apple/GitHub) and a dev-minted id_token, the seeded user@test.com/1234 account is present (a preview has it too), and the anon key is the local one.
/push/*Same codehosted-push.js on auth.db in both modes; register and unregister work locally with the local anon key.
/_dev/*Local onlyGET /_dev/auth, POST /_dev/auth/reset, /_dev/oauth/<provider>, POST /_dev/schedules/<name>/run — loopback only. The seeded account's state and reset, the local provider page and the local schedule trigger are test and developer conveniences; a hosted container never registers the prefix.
dbSame codeOne SQLite file, one single-statement contract; hosted the file is replicated by litestream underneath.
secretsSimulated below a boundaryHosted the values arrive as a file mounted at container start; locally they are read from .snoozestack/secrets.json (snoozestack secrets set --dev), with the linked project's dev namespace as a fallback that is cached into the file.
storageSimulated below a boundarySame five operations plus signed read and upload URLs; hosted against the project's S3 bucket through portal-signed URLs, locally against a directory under .snoozestack.
authSame codeThe identity plane verifies the bearer session in both modes; locally a session the plane does not know falls through to the linked hosted project's JWKS.
pushSimulated below a boundaryHosted the portal's sender signs to APNs with the project's key; locally the send resolves the same registry and is recorded to .snoozestack/outbox/push for the Notifications page (--push=sandbox keeps the hosted sandbox gateway).
signalsSame codeOne store (signals.db) written the same way in both modes.
emailSimulated below a boundaryHosted the plane asks the portal's relay to send the rendered template through SES; locally the same call renders the project's templates and writes the message to .snoozestack/outbox/email for the Emails page.
function-loaderSame codeOne esbuild bundle definition: publish writes it into the published tree, and the dev server builds the same bundle in memory on the first request and again when the import graph changes.
schedulesSame codeThe same cron matcher and timezone handling; hosted the control worker is the clock, locally the dev server ticks every 15 seconds, and `schedules run <name> --dev` fires one now.
signals-quotaHosted onlyHosted only. The per-day quota and retention window are the account's plan limits, relayed to the container by the node agent; they are not knowable offline, and a local store is never purged.
replicationHosted onlyHosted only; `snoozestack dev reset` is the local lifecycle. litestream replicates data.db, auth.db and signals.db from the container to S3; a laptop's .snoozestack is the developer's own and is never replicated anywhere.

Hosted runtime containers#

The hosted service runs each project in an isolated runtime container. Docker deployment is currently an internal hosting detail; a public runtime image and standalone self-hosting configuration are not available.

The isolation model#

Hosted, each project×namespace gets its own container, and the container boundary is the tenant boundary: a runtime is scoped to exactly one app, capabilities are the only path from function code to data and secrets, and the blast radius of anything a function does is its own container. Namespaces — dev locally, default (live) and named previews hosted — each hold genuinely separate state, which is what lets you exercise a preview without touching live data.