apps.yml reference
apps.yml tells a sandbox how to run your product: the toolchain, the backing services, and each app’s setup, startup, routing, and health checks.
Where it lives
The project Configure page stores the shared baseline for each repository. Changes made by an agent inside a thread are saved as workspace-only overrides, so an app can be configured and tested alongside branch-only code without changing other threads. Promote the reviewed document in project settings when the code is available project-wide.
Cloud builds and thread runtime
L0: platform base: The shared operating system and platform tools used to build project images.
L1: warm layer: Cloned repositories and the toolchain declared in gently.apps.yml. Project services and apps are not running.
L2: prepared layer: Reusable dependencies, setup results and prepared service data. Repository setup first runs without services. Failed repository setup runs again after services start, followed by app setup.
Thread runtime: A thread starts from a reusable image and gets its own preview URL and refreshed environment. Services and apps must start before the runtime is ready. Image tier alone does not prove readiness.
L2 builds have no thread preview URL. GENTLY_ENVIRONMENT_URL, DOMAIN and GENTLY_DOMAIN are supplied when a thread runtime is created, not during the image build. Mapping them through env templates cannot create a build-time URL. Do not ask the user to provide one, invent one, or substitute a production URL to satisfy a build guard.
For a setup failure involving a preview URL, inspect the failing command, its subcommands and environment reads. Dependency installation, compilation, migrations and seeds that only use local services must work without a public preview URL. Separate that work from runtime-only URL validation or configuration. Keep required migrations, seeds and checks that protect the isolated development database. Do not skip the whole setup or suppress its errors. Put URL-dependent work in runtime startup, with optional image warm-up tolerant of missing URLs.
A complete example
tools:
- nodejs_22
- pnpm@10.32.1
env:
NODE_ENV: development
setup:
- pnpm install
- pnpm db:migrate
services:
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
connectionEnv:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/app
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 5
apps:
web:
dir_path: apps/web
when:
exists: apps/web/package.json
port: 3000
hmr_port: 30003
routes:
- path: /
startup: pnpm dev --host 0.0.0.0
secrets:
MAPS_API_KEY: "Maps console > Credentials > Server key"
health:
- endpoint: /
api:
dir_path: apps/api
port: 4000
routes:
- path: /api
startup: pnpm start
health:
- endpoint: /healthzTop-level fields
toolsToolchain packages, as name or name@version — e.g. nodejs_22, pnpm@10.32.1, postgresql_16.
envRepo-level environment variables injected into all apps and services.
setupRepo-level installs, migrations and seeds, run before per-app setup. Commands can run again during builds, activation and reconciliation, so they must tolerate existing state.
secretsSecret variable names shared by every app in this repository, each with an optional where-to-get hint. Names only; add values in Gentlybot.
onepasswordBind a 1Password item as the source of repo-level secrets: source_type (note or fields) plus source_item_id.
servicesNamed backing services (Postgres, Redis, …) run as containers next to your apps.
appsNamed applications — the processes Gentlybot starts, routes, and health-checks.
App fields (apps.<name>)
startupCommand(s) that launch the app. The only required app field.
portHTTP port the app listens on. Required for endpoint health checks and URL routing.
hmr_portSeparate HMR/WebSocket port (e.g. Vite). Upgrade requests are routed here instead of port.
rewrite_hostSend Host: localhost:<port> to the app, with the public preview host in X-Forwarded-Host. Only for apps that reject unknown hostnames. Requires port.
dir_pathSubdirectory of the repo the app lives in (monorepos). Defaults to the repo root.
when.existsOptional repo-relative sentinel. The app is skipped in workspaces where this path does not exist.
routesHow the app is exposed on the sandbox URL: each entry is exactly one of path (/api), prefix (a subdomain label), or subdomain.
envApp-level environment variables; override repo-level values.
setupApp-specific setup commands, run after repo-level setup. L2 builds run these with services available, but without a thread preview URL.
healthReadiness checks — all must pass before the app counts as running. Each is one of endpoint: /path (2xx/3xx on port) or exists: relative/file.
secretsSecret variable names only this app needs, each with an optional where-to-get hint. Names only; add values in Gentlybot.
onepasswordApp-level 1Password binding, same shape as the repo-level one.
Service fields (services.<name>)
imageContainer image, e.g. postgres:16 or redis:7.4.
environmentEnvironment variables for the service container.
portsPort mappings, e.g. "5432:5432".
connectionEnvConnection strings exported to your apps’ environment, e.g. DB_URL.
healthcheckContainer health probe: test (command array) plus optional interval, timeout, start_period, retries.
optionsRuntime tuning: memory and shmSize (up to 8g), cpus (up to 4), ulimits, and capAdd (allowlisted — IPC_LOCK, SYS_NICE, SYS_RESOURCE plus Docker defaults). Privileged containers are not available.
Add secrets in Gentlybot
API keys, tokens and signing secrets never go in gently.apps.yml or your repository. The config declares which names each app needs; the values are stored encrypted in Gentlybot, can't be read back, and reach every new sandbox for the project.
Declare the names
Put a name under top-level secrets: when every app in the repository shares one value, such as a server and a background worker from the same codebase. Put it under apps.<name>.secrets when apps need different values, such as apps in separate folders of a monorepo. Names are uppercase server-side variables: names exposed to the browser (VITE_…, NEXT_PUBLIC_… and similar) and names the sandbox sets (PORT, GENTLY_…) are refused.
secrets: # shared by every app in this repository
SIGNING_SECRET: "Generate with: openssl rand -hex 32"
apps:
api:
startup: bin/rails server
secrets: # this app only
PAYMENTS_API_KEY: "Payments dashboard > API keys (test mode)"
worker:
startup: bundle exec sidekiqAdd the values
- On the Configure page: each repository has its own Secrets section, above Tools and Services, with one group per app. A repository with missing secrets opens by default. Use Add value or Replace on a row, Add secret for a new name, or the ⋯ menu to remove a value. Project admins can change values; other members see names and whether each is saved.
- Paste .env: paste
NAME=valuelines to add or replace several secrets at once. Nothing is removed, and browser-exposed or reserved names are skipped with a note. - Edit all: saved values show as
NAME=***. Leave***to keep a value, type over it to replace it, delete a line to remove it, or add lines. You confirm removals before anything is saved, and every change lands together or not at all. - In a thread: when setup needs a value, Gentlybot asks whether you want to enter it now. If you do, a secure form opens; in a project with several repositories you choose the repository it belongs to. Project admins see Use in every new thread for this project, on by default; unchecked, the value reaches that thread only. A thread-only value can be moved to the project later from the thread's Environment tab.
Each value belongs to one app, or to all apps, in one repository. Two repositories can use the same variable name with different values, so app names must be unique across a project's repositories. Values reach the app's environment and its .env file, not repository-level setup: commands. New sandboxes receive them automatically; a running sandbox picks up a change after you rerun setup or restart the app.
Optional: use 1Password for secrets
If your team already keeps development secrets in 1Password, Gentlybot can load them instead of values added above. Keep secret values in 1Password, not in gently.apps.yml or your repository. Gentlybot loads the selected items into the sandbox environment for your apps.
- Create a 1Password service account with read access to the vault containing your development secrets, and copy its service account token.
- On the project’s Configure page, expand 1Password. Paste the token and select Save; then choose the vault and select its Save button. This vault is shared by the project’s item bindings.
- Create an item in that vault using one of the formats below. Copy its item ID (the item’s unique identifier), then expand the repository on Configure and add a
onepasswordblock in itsgently.apps.ymleditor. Save the config and start or refresh the sandbox.
Option 1 · Note body
Put KEY=value lines in the item’s free-text note body, like a .env file. Choose source_type: note. Custom fields on the same item are not read in this mode.
API_TOKEN=your-value
PRIVATE_KEY="your-multiline-value"Option 2 · Item fields
Add a separate Text or Concealed field for each variable. The field label is the variable name (for example API_TOKEN) and the field value is the secret. Choose source_type: fields. The note body is not read in this mode.
Add the block at the top level to share values across the repository’s apps, or under apps.<name> for one app. You can use both with different items. Replace the example IDs with IDs from the selected vault; item titles also work if unique, but IDs avoid ambiguity.
onepassword:
source_type: note
source_item_id: your-repo-item-id
apps:
web:
startup: pnpm dev
onepassword:
source_type: fields
source_item_id: your-web-item-id