Skip to content
Beta

Web app runtime

This page covers how a web app runs once it is built: the port it listens on, background processes beside it, a command for migrations, the machine it builds on, and how a static site is served. How it is built is in Build configuration.

Most of it is set on the web app's General tab, in the Build and Runtime cards. The Runtime card's Save & redeploy saves your changes and starts a new deploy.

The port

Your web process must listen on the port set in Port, 8080 unless you change it; it also gets that port as $PORT. Any port from 1 to 65535 works except 22. A changed port shows Applies on next deploy, and traffic moves to it only once the new version is serving there.

Every deploy checks the new version before it takes traffic. It starts beside the version that is serving, and the platform requests / from it over HTTP on its port for a few minutes:

  • Any answer below 500 passes, so 200, a redirect or a 404 all count as up.
  • A 5xx, no answer, or a process that exits fails the deploy, and the current version keeps serving.

During that check the new version runs with SAHABTI_HEALTHCHECK=1 in its environment. If your app does something once per deploy when it boots, skip it when that variable is set, or it runs twice. Better still, move it to the pre-deploy command.

Background processes

Under Processes, a web app can run background processes beside web, such as a queue worker or a scheduler: up to five, each with a Process name and a Command.

  • Each one runs as its own container, from the same image and with the same environment variables as web, plus PROCESS_TYPE set to its name.
  • A name starts with a lowercase letter and holds up to 30 lowercase letters, digits and dashes. web is taken by your start command, names must be unique, and release is best avoided: the pre-deploy command runs under that name.
  • A command is one line.

They start after the new web is serving, so web and its processes always run the same version. A process that exits is restarted automatically; one that cannot start does not fail the deploy, and the deploy log says so. A process you remove is stopped by the next deploy. Each process runs as one container: there is no setting for more copies.

The Processes tab

The Processes tab shows what is running now, read live from the app's host: web and each background process, with its state, its restarts, its exit code when it stopped, how long it has been up, and the deployment it runs. Logs shows the last 100 lines of a process's output, and Refresh reads everything again.

StateMeans
RunningUp and running
StartingCreated, not running yet
RestartingExited and being restarted
StoppedExited, with its exit code
PausedPaused

A process you declared that is not running is listed under Configured but not running: check its logs from the last deploy, or redeploy.

Pre-deploy command

The Pre-deploy command runs once on every deploy, before the new version receives traffic. Use it for database migrations, for example python manage.py migrate.

  • It runs with the app's environment variables, plus SAHABTI_RELEASE=1.
  • In a JavaScript server app the platform builds, such as Next.js or Node.js, it runs with every dependency installed, development ones included. In other apps, static sites included, it runs in the app's own image.
  • It may take up to 15 minutes.
  • If it fails or runs out of time, the deploy stops and the current version keeps serving.
  • A rollback does not run it.

Because the current version is still serving while it runs, a migration must work with both the old and the new code: add first, deploy, and remove what is unused in a later deploy.

Background processes and the pre-deploy command run through a shell. A Go app the platform builds without a start command runs in an image that has none, so to use either, set a start command or build your own Dockerfile.

Build machine

Build machine, in the Build card, is the machine your build runs on. It is separate from what the app gets once it is deployed:

Build machinevCPUMemoryPlans
Standard24 GBAll
Large48 GBPro and Scale
Extra large416 GBPro and Scale

Choose a larger one when builds run out of memory, which is common for large sites that pre-render many pages. A machine your plan does not include is marked Upgrade required; if the plan changes, builds run on the largest machine it includes.

What stays between deploys

Every deploy replaces the app's containers, so anything a process writes inside its container is gone after the next one. Keep data in an instance, such as a database, or in a service outside the app.

Requests reach your app as plain HTTP, after HTTPS ends at the platform's edge, and the visitor's address arrives in the X-Forwarded-For header.

Private names for your instances

Inside a web app, every instance on the workspace's network has a private name: <instance>.<workspace>.internal, where <instance> is the instance's name with dashes for underscores and <workspace> is the workspace's URL slug. Connect on the engine's own port, for example orders.acme.internal:5432, and the traffic never leaves your workspace network.

The names are written on every deploy, so an instance created after the last deploy resolves after the next one. When you import a repository, Connected resources adds an instance's connection string, pointed at its private name, as an environment variable. The build itself runs on a separate machine that cannot reach your instances, so read from them at runtime or in the pre-deploy command, not in a build step.

Static sites

A web app built as a static site, from the Static framework, a Vite or Create React App single-page app, a static export of Next.js, Astro or SvelteKit, or an output directory with no start command, is served by the platform straight from its output directory:

  • Clean URLs: /about serves about.html, or about/index.html on a site that is not a single-page app. /about.html, /about/ and /about/index.html redirect to /about with a 308.
  • Single-page apps: a Vite or Create React App site answers every path it has no file for with its index.html.
  • Not found: any other site serves its 404.html, when it has one, with the 404 status.
  • Compression and caching: responses are compressed. Files in a framework's hashed asset folder, such as /_next/static/, /_astro/ or /_app/immutable/, are cached for a year, and everything else is revalidated on every load.

Redirects and headers

Put a _redirects file, a _headers file or both at the top of the output directory. They are applied when the site is built and never served themselves.

_redirects holds one rule per line, from to [status]:

text
/old-pricing   /pricing          301
/blog/*        /articles/:splat  301
/docs          https://docs.example.com/
  • The status is 301, 302, 303, 307 or 308, and 302 when left out.
  • from is an exact path, or a path ending in *, whose match :splat places in to.
  • Lines starting with # and blank lines are ignored.

_headers holds a path, then the headers for it on indented lines:

text
/*
  X-Frame-Options: DENY
/assets/*
  Cache-Control: public, max-age=86400
  ! X-Powered-By
  • A path may use * anywhere, matching any run of characters.
  • Name: value sets a header, and ! Name removes one.
  • Your Cache-Control replaces the default one.
  • When two rules set the same header, the later one wins.

A rule outside this subset, such as a :placeholder in a path, a 200 rewrite or a full URL as the source, is skipped and named in the deploy log as _redirects: skipped or _headers: skipped.

Who can do what

Owners, admins and developers can change the runtime settings and the build machine; viewers can see them and the Processes tab. See Workspaces & roles.

From the API and MCP

The API sets the port, processes and pre-deploy command with PATCH /v1/apps/{id}/runtime, which takes effect on the next deploy, and reads running processes with GET /v1/apps/{id}/processes. Over MCP, use update_app_runtime, get_app_processes and redeploy_app.