Skip to content

Manifest Includes

Demo transcript

Each package owns its services. The root starts them together.

devhost supports splitting stack configurations across multiple files using the includes key. This is particularly valuable in monorepo environments, allowing each sub-application or workspace package to maintain and control its own service definitions locally without cluttering a single massive root-level manifest.

In your root-level devhost.toml, define the stack name, any global settings, and specify the includes key with a list of file paths or glob patterns (resolved relative to the directory of the root manifest):

# root devhost.toml
name = "my-monorepo-stack"
includes = [
"packages/*/devhost.toml",
"apps/*/devhost.toml"
]

Each matching sub-manifest is parsed, prepared, and merged recursively into the main stack.

The merged stack holds every service of every included manifest, which is usually more than one task needs. Name the services to run from the root manifest’s directory, and devhost starts those, the services they reach through dependsOn, and the services marked alwaysStart = true:

Terminal window
devhost start web api

The names can come from any included manifest, and dependsOn can cross from one included manifest to another. The services left out run no process and reserve no hostname or port. See Starting part of a stack for the selection rules, a worked example, and how to start a stopped service later.

Groups defined under [groups] merge alongside services. Each app can own its group in its local devhost.toml; the root needs only includes. Group members can reference services from any included manifest, and duplicate group names are rejected. See Named service groups for configuration, startup selection, and reload behavior.

Saving the root manifest or an included manifest reloads the merged service configuration. Adding or deleting files matching an includes glob adds or removes their services without restarting devhost. Devhost validates the whole candidate before changing running services; rejected edits leave the accepted configuration in place. Changing annotation actions requires stopping and restarting devhost, including when the actions come from an included file.

Affected services, their dependents, and affected repository groups restart together. Compatible automatic ports and selected checkouts are retained. Selecting a worktree continues to use the original root manifest and its includes; it does not load the selected checkout’s manifest. See Stack lifecycle for reload boundaries and restoration after launch or routing failures.

Sub-Manifest Solo Runs & Override Protection

Section titled “Sub-Manifest Solo Runs & Override Protection”

To support local solo development, a sub-manifest (such as packages/app1/devhost.toml) can define a complete standalone stack configuration (including its own name, local caddy port mappings, or devtools overlays).

When a sub-manifest is run solo (by running devhost start from its local package directory), it runs as a first-class standalone stack.

However, when included in a root manifest:

  1. Local services (services), groups (groups), and annotation actions (annotation.actions) are extracted and merged.
  2. Root global settings (like stack name, Caddy proxy front door, and devtools toggle settings) remain authoritative and cannot be overridden by the sub-manifests.

This enables you to use the exact same configuration files both for individual sub-application solo runs and as modular components of a large multi-service stack.

Path Resolution & Working Directory Defaults

Section titled “Path Resolution & Working Directory Defaults”

Normally, paths in devhost are resolved relative to the manifest directory. To keep workspace configurations completely self-contained, devhost automatically applies the following rules during merging:

  • Relative Path Rewriting: Any relative cwd or watch folder defined in a sub-manifest is automatically resolved and rewritten relative to the root manifest’s directory before spawning or tracking.
  • Default Working Directory: If a service in a sub-manifest omits cwd, it automatically defaults to the directory of the sub-manifest where that service was defined, rather than defaulting to the root manifest directory.

If the root manifest is at /project/devhost.toml and includes /project/packages/api/devhost.toml:

/project/packages/api/devhost.toml
[services.api]
command = ["bun", "run", "dev"]
# cwd is omitted, so it defaults to "." (the app directory)
watch = ["src/"]

During merging, devhost translates the paths relative to the root manifest directory /project:

  • cwd becomes packages/api
  • watch becomes ["packages/api/src/"]

To ensure configuration robustness and prevent common monorepo pitfalls, devhost enforces these rules at validation time:

  • Unique Service Names: Service names must be unique across all merged manifests. If two manifests define the same service name, devhost aborts with a clear conflict error.
  • Deterministic Order: Glob pattern matches are sorted alphabetically prior to merging, ensuring that the service load order (and fallback primary service selection) remains 100% deterministic across all filesystems and operating systems.
  • Cyclic Reference Protection: If Manifest A includes Manifest B, which includes Manifest A, devhost safely detects the cyclic reference and skips already-visited files without infinite recursion.
  • Shared Variables: Environment variable interpolation (e.g. {{ env.NAME }}) runs on the fully merged map, enabling system variables to propagate cleanly to all sub-manifest definitions.