Start and stop an application
Start the service, prepare required state, run process work, and stop cleanly.
app.start() is the boundary between an application's declarations and its
running process. It connects NATS, prepares shared runtime state, runs
application lifecycle hooks, verifies declared Sync resources, then registers
the live service and returns the Bun-compatible server definition.
The returned server handles /_cloud/ready before the application router.
Because Bun does not start serving until the awaited definition is returned,
the endpoint becomes reachable only after setup, start, Sync readiness,
and registration have completed. Use it for direct container or pod readiness checks.
Pass the Hono fetch handler:
export default await app.start({
fetch: router.fetch,
});Set the start options
The complete shape is:
export default await app.start({
fetch: router.fetch,
openapi: apiRoutes,
lifecycle: {
setup,
start,
stop,
},
capabilities: inventoryCapabilities,
help: inventoryHelp,
port: 3000,
skipSetup: false,
});| Option | Required | Default | Meaning |
|---|---|---|---|
fetch |
Yes | — | Application request handler |
openapi |
No | — | Bare router used to generate OpenAPI |
lifecycle |
No | — | setup, start, and stop hooks |
capabilities |
No | — | Versioned Types, Queries, and Actions |
help |
No | — | App-owned product Help registered with the live service |
port |
No | 3000 |
Internal Bun server port |
skipSetup |
No | false |
Skip the setup hook |
See App capabilities for the executable contract and In-product Help for the Help definition.
OpenAPI also needs the document path declared in defineApp(). See
Typed HTTP APIs.
Lifecycle hooks
| Hook | Use |
|---|---|
setup |
Prepare required state before the server definition is returned |
start |
Start workers, schedulers, and subscriptions |
stop |
Release process resources |
export default await app.start({
fetch: router.fetch,
lifecycle: {
setup: async () => {
await migrate();
},
start: async () => {
await stockWorker.start();
},
stop: async () => {
await stockWorker.stop();
},
},
});Keep HTTP middleware and route mounting outside the lifecycle.
Prepare state in setup
setup runs on every normal start. Database migrations belong here:
setup: async () => {
await migrate();
},Make setup work safe to run more than once. See Migrations and transactions.
skipSetup: true prevents the hook from running. Use it only when another
controlled process already prepared the required state. It must not hide a
failing migration.
Wait for NATS
app.start() needs NATS and JetStream before anything else. When they do not
answer yet, for example because the whole stack restarted at once and
JetStream is still recovering, Cloud retries with a new connection. The first
retry follows after about one second. The delay doubles up to about 20
seconds, with random spread so that processes that restarted together do not
retry in step. Each attempt logs NATS or JetStream is not ready; retrying
with the reason.
The process does not serve /_cloud/ready while it waits. When NATS and
JetStream still do not answer after five minutes, it logs an error and exits
with status 1, also under bun --watch, so that Docker or Kubernetes
restarts it. A start fails at once when waiting cannot help: NATS_SERVERS
is missing, the server version is unsupported, or JetStream is disabled.
Clean up a failed start
Cloud calls the application's stop hook if setup, start, resource
readiness, or registration fails. It then releases notification registration,
watchers, registry entries, and its NATS connection. The application is not
advertised before its hooks and declared Sync resources are ready. Database
writes and external effects are not rolled back.
app.start() then rejects. A process started with bun, such as a built
image, ends with status 1 and its restart policy starts it again. Under
bun --watch, the process stays alive without readiness and starts again only
when a watched file changes. Only a NATS wait that runs out of time ends the
process under bun --watch as well.
Make stop safe after partial startup. When a hook has several steps, it can
also release completed steps locally:
start: async () => {
await importWorker.start();
try {
await reconciliationWorker.start();
} catch (error) {
await importWorker.stop();
throw error;
}
},See Lifecycle background work.
Stop in reverse order
Cloud calls stop for SIGTERM and SIGINT.
Stop resources in the reverse order from startup:
stop: async () => {
await reconciliationWorker.stop();
await importWorker.stop();
},After the hook, Cloud removes notification registration. It then stops the runtime watcher and removes the application registry entry. Sync workers drain before the NATS connection closes. Stop and drain application workers before releasing the database clients or other dependencies they use.
See Scaling and shutdown for deployment behavior and shutdown deadlines.
Lifecycle context
Each hook receives:
setup: async (cloud) => {
const log = cloud.logger("inventory");
log.info("Preparing inventory");
const timezone = await cloud.settings.get<string>("app.timezone");
const applications = cloud.runtime.apps;
},The context contains:
logger(source)for structured application logs;- asynchronous
settings.get()andsettings.set(); - a snapshot of registered applications;
sync, the process-owned Sync instance for distributed primitives.
Request handlers should use request middleware instead. See Settings and Request middleware.
Startup order
Cloud starts the application in this order:
- require the shared
APP_SECRETand connect the process-owned NATS instance, waiting while NATS and JetStream do not answer; - declare registry resources and start the runtime watcher;
- run
setup, unless skipped; - register notification definitions;
- load the settings cache;
- run
start; - await readiness of all Sync resources declared during startup;
- publish Help and capability records, then advertise the application;
- return the server definition.
Every application container needs the same non-empty APP_SECRET. Startup
fails before registration when it is missing.
The returned object contains port, development, and fetch. Applications
that expose Bun WebSockets add their handlers to that result:
const result = await app.start({ fetch: router.fetch });
export default {
...result,
websocket,
};