while.yaml
while.yaml, at the root of your app, says what it needs. Your agent writes it, and this page lists every field.
Updated November 9, 2026.
apiVersion: while/v1
project: planning
services:
- name: web
port: 3000
health: { path: /health }
databases:
- name: main
This file describes a project: its services, its databases, its caches, its storage, its queues, its scheduled jobs, its domains, its environments. while builds its infrastructure. A project without a web service is allowed, if it declares at least a database or a scheduled job.
The file
| Field | Type | What it does |
|---|---|---|
apiVersion (required) | always while/v1 | The format version: always while/v1. |
project (required) | text (lowercase) | The project’s identifier, in lowercase. |
services | list of services | The app’s services. They are optional when the project declares at least a database or a scheduled job: a data project does not always have a web service. |
databases | list of databases, or of other projects’ databases | The project’s PostgreSQL databases, or databases of other projects that it reads. Four at most. |
caches | list of caches | The project’s Valkey caches, which speak the Redis protocol. Four at most. |
buckets | list of buckets | The project’s S3 buckets, for its files. Four at most. |
crons | list of scheduled jobs | The scheduled jobs: a command run at a fixed time, in a service’s image, or built for it when the project has no service. Each run is tracked (history, failures, missed runs), and can be run again or caught up on request. |
environments | object | The settings specific to an environment. The key is the environment’s name: production, preview… |
telemetry | object | What while keeps of the app: its logs, its metrics, its traces and its errors. |
telemetry.logs | true or false | true keeps the app’s output in its logs. Default: true. |
telemetry.metrics | true or false | true counts the app’s requests, errors and response times. Default: true. |
telemetry.traces | true or false | true keeps the app’s OpenTelemetry traces. Default: false. |
telemetry.errors | true or false | true gives the app a Sentry-compatible entry point, whose address (DSN) while provides. Default: true. |
telemetry.uptime | object | An availability probe: a path of the app, called at a regular interval. |
network | object | The app’s network egress. The Internet is CLOSED to it by default (egress: restricted): an external API is declared as a connection (declare_connection), while adds its key and goes over IPv4 if needed, and the app calls it through <NAME>_URL. egress: all opens the Internet directly, after a person’s approval. A change holds at the next deployment. |
network.egress | none, restricted, all | none closes all access to the Internet. restricted (default) closes the Internet, except the declared connections and the allow list, which go through while’s egress. all opens the Internet directly, over IPv6 as over IPv4, after approval. Default: restricted. |
network.allow | list of texts | The host names allowed when egress is restricted. They are called through while’s egress, never directly. For an API that requires a key, prefer a connection. |
Services
Each entry in services describes a service: a site, an API, a worker or a static site.
| Field | Type | What it does |
|---|---|---|
name (required) | text (lowercase) | The service’s name, in lowercase. It goes into its address and into SERVICE_<NAME>_URL. |
description | text | A line for whoever opens the service, under its title on the page of their apps (“Today’s sales, per store”): 140 characters at most, with nothing technical. Without it, the page shows the address. |
type | web, worker, static | web: an HTTP service, with its address. worker: a process without an address or an HTTP probe, such as a job queue or a consumer. static: a static site, served with an HTTP cache. Default: web. |
root | text | For a static site: the folder to serve once the site is built (dist, build, public…). Default: .. |
source | text | The path of the sources in the uploaded archive, or in the git repository. Default: .. |
image | text | An image already built, in an external registry or in while’s, preferably with its digest. |
dockerfile | text | The path of the Dockerfile. Without it, while recognizes the language and writes the Dockerfile. |
build | object | The build of the service’s image. |
build.context | text | The build’s context folder. Default: .. |
build.target | text | The Dockerfile stage to build (—target). |
build.args | object | The build arguments (—build-arg), not secret: they stay in the image’s history and provenance. A name that looks like a secret (TOKEN, KEY, SECRET, PASSWORD) is refused: go through secrets. |
build.secrets | list of texts | The names of the build secrets, such as NPM_TOKEN. Their value is set separately, with set_env { build: true } or the “for the build” box of the settings, never here. They are mounted for the time of a RUN in /run/secrets/<NAME>, never in the image, and masked in the log. A build never reads the app’s secrets, and the app does not receive the build’s. |
build.test | text | The test command, run in the same sandbox as the build. By default, that of the recognized language. |
build.skip_tests | true or false | true does not run the tests when building this service. Default: false. |
command | list of texts | The command that starts the service, instead of the image’s, for example ["node", "server.js"]. |
port | integer | The port the service listens on. while gives it to the app in the PORT variable. Default: 8080. From 1 to 65535. |
public | true or false | true exposes the service on a public domain. Default: true. |
access | public, internal | public opens the service to the whole Internet. internal puts it behind while’s connection, or the company’s: the app receives the identity in X-While-Email, X-While-Name, X-While-Groups and X-While-Identity. Without groups, emails or email_domains, the organization’s members get in. Making an internal app public, or changing who gets in, requires an approval. Default: public. |
emails | list of texts | For internal access: the addresses that get in, members of the organization or not. |
domains | list of texts | The service’s custom domains. while always gives it its default address too. |
health | object | The health probe: while calls this path every second, and a version only receives traffic once it answers 2xx or 3xx. |
health.path | text | A path of the app. It starts with a single ”/”, without a full address, without ”//”, without a backslash or a space. Default: /health. |
health.initial_delay_seconds | integer | The delay before the probe’s first call, in seconds. Default: 0. |
health.timeout_seconds | integer | The time the probe waits for an answer, in seconds. Default: 3. |
health.startup_timeout_seconds | integer | The time a new version has to answer its probe, in seconds, before being refused (the live version keeps the traffic). 120 by default, 900 at most: for an app that migrates its database at startup (Metabase, Keycloak). A truly broken version then takes that long to be declared failed. Default: 120. From 30 to 900. |
env | object | The environment variables. A value { secret: path } is read from while’s vault. |
resources | object | The processor and memory reserved for each instance of the service. |
resources.cpu | CPU (500m, 1) | A processor quantity: 500m for half a vCPU, 1 for one vCPU. Default: 200m. |
resources.memory | memory (256Mi, 1Gi) | A memory quantity: 256Mi, 1Gi. Default: 192Mi. |
resources.disk | disk (1Gi) | For a worker or a task: the /tmp disk, on the node’s disk and not in memory, which is also the container’s storage limit. 1 GiB by default, 8 GiB at most. No effect on a web service, whose /tmp is taken from its memory. Default: 1Gi. |
volume | volume | Not open yet on this platform: deployment refuses a volume. A disk that survives deployments, restarts and the loss of a node, for a worker or a task, mounted by one instance at a time. Removed from the while.yaml, it is kept 7 days, then destroyed. |
scale | object | The number of instances of the service, between min and max. |
scale.min | integer | The number of instances kept as long as the service does not sleep (see sleep). 0 is the old way of saying sleep: 30m: prefer sleep. Default: 1. From 0 to 5. |
scale.max | integer | The cap of instances when traffic rises: 10 at most if scale.max is not declared. Default: 1. From 1 to 5. |
uses | list of texts | The databases, caches, storages or queues this service uses. By default, everything the project declares. |
title | text | The title people see, written from the point of view of whoever uses it (“Sales reading”, “Order import, every hour”): 80 characters at most, on one line, with nothing technical. Without a title, the screens show the name. |
sleep | delay (30m, 2h) or never | Putting a web service or a static site to sleep. A delay (30m, 2h) puts it to sleep after that time without a request: the next request wakes it up in a few seconds, and waits for its answer. no (or false) always keeps at least one instance. By default: no in production, 30m in the other environments, such as previews. A sleeping service restarts with one instance on wake-up: scale.min only counts if it does not sleep. A worker never sleeps, nor does a production database. |
groups | list of texts | For internal access: the organization’s groups that get in, made in while or coming from the enterprise connection. |
members | true or false | For internal access with groups, emails or email_domains: true also lets the organization’s members in. Without this field, only the lists get in. Without lists, the members already get in. |
email_domains | list of texts | For internal access: the email domains that get in. A domain holds exactly: nextmap.io does not open fr.nextmap.io. |
connections | list of connection names | The organization’s connections, external APIs declared in while, that this service or task calls. For “meteo”, the app receives METEO_URL, the relay’s address, token included. The key stays in while, and each call is traced. |
Databases
Each entry in databases declares a PostgreSQL database. An entry with from reads another project’s database, without creating anything.
| Field | Type | What it does |
|---|---|---|
name (required) | text (lowercase) | The database’s name, in lowercase. It goes into its variables, such as DB_<NAME>_URL. |
engine | postgres | The database engine: postgres, the only one. Default: postgres. |
version | text | The PostgreSQL version: 16, 17 or 18. Default: 17. |
size | memory (256Mi, 1Gi) | The database’s disk, billed per GiB per month. Default: 1Gi. |
compute | S, M, L, XL | An instance’s size: the reserved processor and memory, with the PostgreSQL and pooler settings that follow. S: 0.1 vCPU and 256 MB (up to 1 GB). M: 0.5 vCPU and 1 GB. L: 1 vCPU and 4 GB. XL: 2 vCPU and 8 GB. A size change restarts the database. Do not confuse it with size, the disk. Default: S. |
pool | transaction, session | The mode of the pooler (PgBouncer) in front of the database. transaction, the default: each transaction can take another server connection; many clients for few connections, but what depends on a session only lasts its transaction. session: one server connection per connected client, kept as long as it stays connected; the pooler accepts no more clients than the database size has connections (S 20, M 60, L 100, XL 140), the extra client is refused. Choose session for an app that needs its session: Liquibase migrations (Metabase, Keycloak) or Flyway, which take a session advisory lock, a session advisory lock taken by the app, LISTEN/NOTIFY, a temporary table. Default: transaction. |
instances | 1, 2 or 3 | The number of PostgreSQL instances of the database. With 1, a single instance runs on a node’s disk, and a node failure is repaired by a restore. With 2 or 3, the database is highly available: one or two standby instances are kept up to date continuously, and take over on their own if the primary falls. Each instance has its own disk (size). Do not confuse it with replica, a read replica for query_database. |
replica | true or false | Planned: a read replica, kept up to date continuously, which the agents’ query_database tool will read. The field is accepted already. As long as the replica is not served, query_database reads the primary, in a read-only transaction, and says so in its answer. Default: false. |
backup | object | The database’s backups. |
backup.schedule | text | The time of the day’s full backup, in cron format, in UTC. Default: 0 3 * * *. |
backup.retention_days | integer | Read but not followed: while keeps backups 14 days, for all databases. Default: 7. |
backup.test_restore | true or false | while replays the restore of each backup, and shows the result. Default: true. |
Read another project’s database
The database of another project of the same organization, which this project’s apps read: nothing is created. The first deployment that declares it requires the approval of that database’s approvers. Once approved, each reading app receives DB_<NAME>_URL, DB_<NAME>_USER and DB_<NAME>_PASSWORD, a read-only access that the approvers can restrict to schemas, tables or columns. A database of another organization is refused.
| Field | Type | What it does |
|---|---|---|
name | text (lowercase) | Its name in this project, which makes the DB_<NAME>_URL variable. By default, <project>-<database> (DB_ENTREPOT_MAIN_URL). |
from (required) | text | <project>/<database>: a project of the same organization, and the name of its database. |
access | read | read: the only value, read-only. Default: read. |
environment | text | The source database’s environment. Default: production. |
readers | list of texts | The apps that read it: a service, or task:<name>. Without this field, all those of the project. |
Caches
Each entry in caches declares a Valkey cache, which speaks the Redis protocol.
| Field | Type | What it does |
|---|---|---|
name (required) | text (lowercase) | The cache’s name, in lowercase. It goes into its variables, such as CACHE_<NAME>_URL. |
engine | valkey | The cache engine: valkey, the only one. Default: valkey. |
memory | memory (256Mi, 1Gi) | The memory reserved for the cache. Default: 64Mi. |
durable | true or false | true keeps the data on disk (AOF journal), without ever evicting it: for a job queue (BullMQ, Sidekiq, Celery). Without it, a restart empties the cache. Default: false. |
Buckets
Each entry in buckets declares an S3 bucket.
| Field | Type | What it does |
|---|---|---|
name (required) | text (lowercase) | The bucket’s name, in lowercase. It goes into its variable, such as S3_BUCKET_<NAME>. |
public | true or false | true opens the reading of objects to everyone, without a key, after an approval. Default: false. |
cors | list of texts | The extra origins from which a browser opens a presigned URL of this bucket (https://host[:port]). The app’s are already there. |
Scheduled jobs
The scheduled jobs: a command run at a fixed time, in a service’s image, or built for it when the project has no service. Each run is tracked (history, failures, missed runs), and can be run again or caught up on request.
| Field | Type | What it does |
|---|---|---|
name (required) | text (lowercase) | The task’s name, in lowercase. |
schedule (required) | text | The schedule in cron format, with 5 fields, in the timezone’s zone (Europe/Paris by default): “0 3 * * *” runs the task every day at 3 am. |
command (required) | list of texts | The command to run, for example ["node", "cron.js"]. |
service | text (lowercase) | The service whose image and variables the task takes: the first service by default. Without a declared service, or with image, source, dockerfile or build, the task is built for itself. |
image | text | An image already built for a task without a service, preferably with its digest. |
source | text | For a task without a service: the path of the sources in the uploaded archive, or in the git repository. Default: .. |
dockerfile | text | For a task without a service: the path of the Dockerfile. Without it, while recognizes the language. |
build | object | The build of the service’s image. |
build.context | text | The build’s context folder. Default: .. |
build.target | text | The Dockerfile stage to build (—target). |
build.args | object | The build arguments (—build-arg), not secret: they stay in the image’s history and provenance. A name that looks like a secret (TOKEN, KEY, SECRET, PASSWORD) is refused: go through secrets. |
build.secrets | list of texts | The names of the build secrets, such as NPM_TOKEN. Their value is set separately, with set_env { build: true } or the “for the build” box of the settings, never here. They are mounted for the time of a RUN in /run/secrets/<NAME>, never in the image, and masked in the log. A build never reads the app’s secrets, and the app does not receive the build’s. |
build.test | text | The test command, run in the same sandbox as the build. By default, that of the recognized language. |
build.skip_tests | true or false | true does not run the tests when building this service. Default: false. |
env | object | The environment variables. A value { secret: path } is read from while’s vault. |
uses | list of texts | For a task without a service: the databases, caches, storages or queues it uses. By default, everything the project declares. |
resources | object | By default, those of the service taken, and 100 mCPU and 128 MiB for a task without a service. |
resources.cpu | CPU (500m, 1) | A processor quantity: 500m for half a vCPU, 1 for one vCPU. |
resources.memory | memory (256Mi, 1Gi) | A memory quantity: 256Mi, 1Gi. |
resources.disk | disk (1Gi) | For a worker or a task: the /tmp disk, on the node’s disk and not in memory, which is also the container’s storage limit. 1 GiB by default, 8 GiB at most. No effect on a web service, whose /tmp is taken from its memory. |
volume | volume | Not open yet on this platform: deployment refuses a volume. A disk that survives deployments, restarts and the loss of a node, for a worker or a task, mounted by one instance at a time. Removed from the while.yaml, it is kept 7 days, then destroyed. |
timeout_seconds | integer | The maximum duration of a run. Beyond it, the run is stopped and counted as “timed out”. Default: 600. From 10 to 21600. |
retries | integer | The number of new attempts after a failure, within the same run. Default: 0. From 0 to 5. |
concurrency | forbid, allow, replace | forbid prevents two runs at the same time, and refuses a manual run as long as another one runs. allow permits overlap. replace makes the new run stop the current one. Default: forbid. |
timezone | text | The schedule’s time zone, as an IANA name: Europe/Paris, UTC… Default: Europe/Paris. |
title | text | The title people see, written from the point of view of whoever uses it (“Sales reading”, “Order import, every hour”): 80 characters at most, on one line, with nothing technical. Without a title, the screens show the name. |
connections | list of connection names | The organization’s connections, external APIs declared in while, that this service or task calls. For “meteo”, the app receives METEO_URL, the relay’s address, token included. The key stays in while, and each call is traced. |
Environments
The settings specific to an environment. The key is the environment’s name: production, preview…
| Field | Type | What it does |
|---|---|---|
from | text (lowercase) | The reference environment this one inherits from. |
services | list of objects | The services’ settings in this environment. |
databases | list of objects | The databases’ settings in this environment. |
ttl_hours | integer | The lifetime before automatic destruction, for a preview environment. From 1 to 720. |
The full schema
The JSON schema of while.yaml is public, at https://app.while.sh/schema/while.v1.json. Your agent reads it to check its file before deploying.