---
title: "What your app must follow to run on while.sh"
url: https://while.sh/en/docs/get-started/the-while-contract/
updated: 2026-11-09
---

# What your app must follow

A few rules are enough for an app to run on while. Your agent knows them and applies them: this page tells you too.

## Listen on the port in PORT

Your app listens over HTTP on the port in the PORT variable: the one in while.yaml, 8080 by default. It listens on all interfaces (`0.0.0.0`), not only on localhost.

## Answer on the health path

Your app answers 2xx or 3xx on its health path (`health.path`, `/health` by default) as soon as it can serve.

- while calls it every second.
- A release that does not answer never receives traffic: while removes it on its own, and the previous release stays online.
- A new release has 120 seconds to answer; an app that migrates its database at startup (Metabase, Keycloak) asks for more with `health.startup_timeout_seconds`, up to 900.

## Pass its tests at build time

The project's tests run during the build (npm test, pytest, go test).

- If they fail, nothing is deployed.
- An empty or missing test script is skipped.
- When several services share the same sources, the tests run once, and `build: { skip_tests: true }` turns them off for one service.
- With your own Dockerfile, while only runs its "test" stage.

## Run without privileges

Your app runs without privileges, never as root. A numeric USER that is not root is respected, for example `USER 1001:1001`. A named USER (`USER node`), root or missing becomes `1000:1000`, the user that the Dockerfiles generated by while declare.

## Write its logs to standard output

Your app writes its logs to standard output and standard error. while keeps them, and your agent reads them with the `get_logs` tool.

## Keep its files in the right place

A worker and a task have a 1 GiB `/tmp`, which takes nothing from their memory, and `resources.disk` raises it up to 8 GiB. A web service's `/tmp`, on the other hand, takes from its memory: its large files go in a worker, a database or a bucket. A `/tmp` is emptied at each restart, so what must last goes in a database or a bucket.

## Read a secret set as a file

A secret set as a file is read in `/run/secrets/<NAME>`, never in a variable.

- It is set with `set_env` and `file: true`, or with `{ secret: …, file: true }` in while.yaml.
- In Node: `(await readFile('/run/secrets/API_KEY', 'utf8')).trim()`.
- In Python: `Path('/run/secrets/API_KEY').read_text().strip()`.
- In Go: `os.ReadFile("/run/secrets/API_KEY")`, then `strings.TrimSpace`.
- In a project linked to a federated connection that requires approval of each production deployment, it is the only way to receive a secret: variables there are shown in clear text to whoever approves.

## Call the internet through a connection

The internet is closed to the app by default: it only reaches its other services, its databases, its caches and while.

- It also is with `egress: none`, or with an allow list, which opens nothing directly.
- To call an external API, go through a connection: with `connections: [meteo]`, the address arrives in `METEO_URL`, the key stays in while, and every call is traced.
- An API without IPv6 then goes through while's IPv4 `egress`.
- `network: { egress: all }` opens the internet directly, over IPv6 as over IPv4, if the organization's rules allow it, except the platform's internal networks and port 25 (SMTP).

## Choose who opens the app

A service is public by default.

- `access: internal` puts it behind while's sign-in, and the app has nothing to code for authentication.
- It reads the identity in `X-While-Email`, `X-While-Name` and `X-While-Groups` (percent-encoded), or in `X-While-Identity`, a JWT.
- while removes any `X-While-*` header coming from the client.

## Stay within its resources

By default, a web service has 200 mCPU and 192 MiB of memory, and a worker 100 mCPU and 128 MiB; resources changes them.

- A container asks for 8 GiB of memory at most, and up to 16 GiB if the organization's memory quota allows it.
- An organization asks for at most 4 vCPU and 8 GiB of memory by default, all its projects and environments together; the `get_usage` tool (quotas view) gives its own.
- Each service counts in it for its maximum number of instances: `scale.max` (10 without a setting), without exceeding the "instances" limit (3 by default).
- A web service without `scale.max` therefore counts 3 times its resources, since it scales up to that with traffic.
- Tasks, caches and databases count too, a database for 100 mCPU and 256 MiB per instance.

## Exit with code 0

A scheduled job (`crons`) exits with code 0 when all is well. Any other code is a failure: the agent receives it with the summarized error and the last lines of the log (`list_tasks` and `run_task` tools).

## Try against a real database

To try against a real database, no need to run one on your machine.

- Deploy: the database is born with the environment, and the following versions build in about a minute.
- Then run the task (`run_task`, with wait), then read the result (`query_database`).
- A failure gives the error and what to do.
- A preview lets you try without touching production.

## The variables that are always there

Your app always receives these variables, on top of those of its building blocks:

| Variables | What they say |
|---|---|
| `PORT`, `HOST` | where to listen |
| `WHILE_PROJECT`, `WHILE_ENVIRONMENT`, `WHILE_SERVICE`, `WHILE_RELEASE` | the project, the environment, the service and the version |
| `WHILE_CRON`, `WHILE_TRIGGER` | in a task: which one, and how this run was started (scheduled, manual or catch_up) |
| `WHILE_CATCH_UP_START`, `WHILE_CATCH_UP_END` | the period of a catch-up |
| `NODE_ENV` | production in production, development elsewhere |
| `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_SERVICE_NAME` | OpenTelemetry telemetry |
| `SENTRY_DSN` | errors, with any server-side Sentry SDK (an internal address, reachable without opening network egress) |
| `SENTRY_DSN_PUBLIC` | errors, with a Sentry SDK in the browser |
| `DATABASE_URL`, `REDIS_URL…` | those of the declared building blocks, and those set with set_env |
