Compose mode
mode: compose plugs WorktreeOS into your repository’s existing Docker Compose
file instead of generating its own. Use it when your project already describes
its topology through docker-compose.yaml and you want WorktreeOS sessions,
status, logs, and the UI on top of it without duplicating definitions.
In this mode the app and deps fields are forbidden. host_ports,
clone_volumes, and cache continue to work.
Example
Section titled “Example”mode: compose
clone_volumes: - .env.local
host_ports: range: start: 20000 end: 29999
compose: config: docker-compose.yaml expose: - api:3000 - api:4000 - name: web port: 5173 tunnel: true env_file: - .env.compose - .env.compose.local environment: DEPLOY_TAG: dev API_HOST_PORT: ${expose.api.hostPort[3000]} WEB_HOSTNAME: ${expose.web.hostname[5173]} WEB_URL: ${expose.web.url[5173]}Fields
Section titled “Fields”compose.config— path to the Docker Compose file. Relative paths resolve against the worktree root; absolute paths are used as-is. The file must exist atwos uptime, and the user-owned file itself is never overwritten.compose.expose— required, non-empty list of exposed ports. Each entry is either the stringservice:portor an object{ name, port, tunnel? }. Bare service names without a port (api) are not supported and cause a validation error (see migration below). Only these services appear inwos status, in the UI, have active log subscriptions, and accept stop/restart actions.compose.env_file— env files passed to thedocker composeprocess. Loaded in order; later files override earlier ones.KEY=valuelines, blank lines, and#comments are supported; unparseable lines fail with the file and line number.compose.environment— inline environment variables that overridecompose.env_file. They support WorktreeOS template substitution:${expose.<service>.hostPort[<port>]},${expose.<service>.hostname[<port>]}and${expose.<service>.url[<port>]}(the full reachable URL — the public tunnel URL when a tunnel is open, otherwisehttp://localhost:<hostPort>).
What WorktreeOS does in compose mode
Section titled “What WorktreeOS does in compose mode”- Assigns a stable host port for each
compose.exposeentry (same allocator andhost_portsas generated mode). - Writes two WorktreeOS-owned copies under
<wos-home>/sessions/<session>/:compose-base.yaml— a copy of your Compose file withservices.*.portsremoved (original publications are dropped to avoid cross-worktree conflicts);compose-overlay.yaml— an overlay publishing only thecompose.exposeports on WorktreeOS-assigned host ports.
- Runs Docker Compose with
-f compose-base.yaml -f compose-overlay.yamlfor every command (up,down,ps,logs,stop,rm). - Resolves
compose.environmentafter port allocation and tunnel preparation. - Injects
WOS_SERVICE_PORTandWOS_SERVICE_HOSTNAMEinto eachcompose.exposeservice through the WorktreeOS-owned overlay, describing the service’s first exposed port (the allocated host port and its active tunnel hostname, orlocalhostwhen no tunnel is active). The overlay is merged aftercompose-base.yaml, so these wos-owned values win over any values set for the same keys in your Compose file. - Registers tunnel routes for each
compose.exposeentry when tunneling is enabled and the run is not--no-tunnel. - Retries
docker compose up -don a WorktreeOS-managed port conflict (up to three attempts), reallocating and rewriting the overlay each time.
What compose mode does NOT do
Section titled “What compose mode does NOT do”- Does not modify the user-owned Compose file.
- Does not publish ports missing from
compose.expose; anyservices.*.portsin the source file is dropped. - Does not inject
compose.environmentinto each container — that environment is thedocker compose ...command environment only (useful for${VAR}substitution inside the Compose file), not a per-container injection mechanism. Per-container service variables come from the wos-owned overlay (WOS_SERVICE_PORT/WOS_SERVICE_HOSTNAME, above) and your Compose file. - Does not run app-port healthchecks and does not execute
app.init_script(theappanddepsfields are forbidden here). - Does not support selective startup — running with an explicit service list or a target in compose mode is rejected.
Migrating from bare service names
Section titled “Migrating from bare service names”Earlier versions accepted bare service names in compose.expose:
compose: expose: - api - workerNow every entry must specify a concrete container port:
compose: expose: - api:3000 - worker:5000Bare names fail validation with a message stating the required service:port
format. If a service has multiple ports, list them as separate entries.