Compose overlays — the [[overlay]] table¶
Most of what a mode does to your compose stack is "add this extra compose file when the mode
matches". That is plain config: an [[overlay]] array of tables in foldyard.toml, matched
against the current mode. No plugin code is needed.
(A mode is a set of switches, each at a level — e.g. gcp=sa llm=live. See the
glossary.)
Schema¶
[[overlay]]
file = "dev-stack/compose.identity.yml" # required — relative to the checkout, or absolute
when = { gcp = "sa" } # optional — see matching below
env = "FOLDYARD_GCP_IDENTITY_COMPOSE" # optional — an env var that overrides `file`
file— the compose file to add as another-f. A relative path resolves against the checkout's root. A file that doesn't exist is skipped silently, so an overlay for a part of the stack a branch doesn't have yet does nothing rather than failing.when— a table ofswitch = levelorswitch = [levels]. The entry matches when every named switch is at one of its levels: AND across keys, OR within a list. A missing or emptywhenalways matches — an unconditional base overlay.env— the name of an environment variable that, when set, replacesfilefor that entry. An escape hatch for tests and CI to point one overlay at a scratch file; normal runs don't set it.
Order: declaration order is -f order¶
Matching overlays are added after your configured compose files, in the order they appear in
foldyard.toml. docker compose lets a later -f file override an earlier one, so the table's
order is the base → override order. Put the file others build on first (e.g. an identity
overlay before the overlays that use that identity), and an app override before its data-layer
twin.
Only matching entries are added, but they always keep their declaration order — so you reason about the stack once, in one place.
Worked example¶
[[overlay]] # base: point the app at the identity emulator
file = "compose.identity.yml"
when = { gcp = "sa" }
[[overlay]] # AND: both must hold
file = "compose.auth0-real.yml"
when = { auth0 = "real", gcp = "sa" }
[[overlay]] # OR within one switch
file = "compose.llm.yml"
when = { llm = ["record", "live"] }
[[overlay]] # added AFTER compose.llm.yml (later -f wins)
file = "compose.llm-live.yml"
when = { llm = "live" }
| mode | -f overlays (in order) |
|---|---|
gcp=sa |
compose.identity.yml |
gcp=sa auth0=real |
compose.identity.yml, compose.auth0-real.yml |
auth0=real (no gcp=sa) |
(none — the AND isn't satisfied) |
llm=live gcp=sa |
compose.identity.yml, compose.llm.yml, compose.llm-live.yml |
How it fits the mode system¶
- A mode change updates the running stack. The overlay list is part of what foldyard compares
across a
fy modechange (with the mode-derived environment). If it changes, foldyard re-creates exactly the affected containers of a running stack — nothing to do by hand. fy stateshows the overlays the current mode wants on itsstackrow, and names any container whose rendered config no longer matches them (the checkfy upitself makes).- A plugin can offer a switch only when an overlay uses it. A switch that would change nothing
without an overlay can hide itself: foldyard collects every switch named in any
when, and a plugin offers the switch only if it's in that set. For example, the built-ingcpplugin offers itsstorageswitch only when some[[overlay]]haswhen = { storage = "staging" }.
When you still need a plugin¶
when expresses "these switches at these levels" — which covers almost every overlay. A plugin's
compose_overlays(mode) hook remains for the rare condition when can't state (a negation, a
value derived from several switches, a path computed at run time); plugin overlays are added
after the table's. Plugins also keep what config can't express: switch declarations and their
levels, token services, and staged assets. Requirements between switches are data — in the
plugin (Switch.requires) or in your config ([[require]], see
configuration).