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.
Updated November 9, 2026.
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_envandfile: 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"), thenstrings.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 inMETEO_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: internalputs 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-NameandX-While-Groups(percent-encoded), or inX-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_usagetool (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.maxtherefore 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 |