Environments
An environment composes templates into an application stack — postgres +
backend + frontend. It belongs to a project and is a pure definition: creating one starts
nothing. What runs is an instance of it.
Environment services
Adding a template to an environment creates an environment service. Its name is unique within
the environment and plays exactly the role a service key plays in a docker-compose.yml: it is
what siblings resolve over the network, and it prefixes the deployed container names. It defaults
to the template's name, so the same template can be added twice under different names.
The name is a lowercase slug — a letter or digit, then letters, digits and dashes, up to 40
characters. That is not a house style: the name is concatenated into container names, volume
names and the ingress router that publishes the service, so web-api works and Web API or
web.api cannot. Leave the field empty and the template's own name is used, which is already in
that shape.
Each environment service carries:
- An image tag — the version this environment runs. See versions below.
- Environment variable overrides — applied on top of the template's own values.
- Injected files — see files below.
- A start wave — an integer,
0by default. - A job flag — for run-once work like a migration.
Start order: waves
Services with the same wave number start together; a wave does not begin until the previous one is ready. "Ready" means healthy for services that declare a healthcheck and started for those that do not — which is the practical reason to give databases a healthcheck.
wave 0 postgres, redis ← start together
wave 1 migrate (job) ← waits for wave 0 to be healthy
wave 2 api, web ← waits for the migration to exit 0
Wave 0 means "no ordering", so a stack with no waves configured simply starts everything at once.
The whole readiness wait is bounded by fadebox.deploy.ready-timeout (default 5 minutes).
Jobs
Marking an environment service as a job changes four things: restart is forced off regardless
of what the template says, the next wave gates on the container completing successfully,
readiness counts it done only once it has exited 0, and it re-runs on every deploy. Jobs must
therefore be idempotent — a migration that fails when re-applied will fail the second deploy.
Versions live on the environment
Each environment service may set an image tag — "this environment runs api:1.4.0". Left
empty, the template's own tag applies. The tag replaces the tag only; the repository stays as the
template defined it, and the retag follows the image, not the container: helpers built from the
main service's repository (a migration container running the app image) get the same tag, while a
helper on a foreign image keeps its own.
The tag can also be a git reference
({{git:app-repo:deploy/values.yaml#$.image.tag}}), resolved fresh on every deploy — the GitOps
values file stays the single source of truth and an ordinary release needs no write to Fadebox.
Because versions are part of the definition, every instance of an environment runs the same versions by default. "The same stack at other versions" is another environment — see cloning below — unless you deliberately hand the choice to whoever deploys, which is what open services are for.
What deployers may change
An environment normally decides everything: an instance is a name and a runtime, and what it runs
is the definition. That is right when the people making instances are the people who wrote the
definition, and wrong as soon as they are not — a tester who needs 1.3.2 to reproduce a bug, a
trainer who wants one seed dataset per attendee, a support engineer chasing a customer's data.
Handing them editor so they can clone is the wrong answer: it hands them the whole definition,
credentials included.
Instead, an environment names the services a deployer may vary, and in what. Each service row carries two switches, both off:
| Switch | What a deployer may then set on that service |
|---|---|
| Variables | Environment variables on the service's containers. |
| Image tag | The service's image tag — the repository stays as the template fixed it. |
In the UI they are the Deployers may change control on the service row, editable by an
editor and shown locked on a protected environment as every other
composing write is. fadebox env service set and the assistant's set_service_access do the
same thing.
Nothing else is delegable, and that is the boundary the feature rests on: a deployer sets
values, never structure. Mounts, ports, networks, labels, privileged and user live in the
template, out of reach. An environment that opens nothing behaves exactly as it always has.
The switches are part of the definition, so cloning copies them and a blueprint keeps them. Exporting to Compose ignores them — they are not a Compose concept. What a deployer then does with them is on the instance.
Where environment variables come from
Three layers, merged at deploy time. Later layers win:
- Environment shared variables — key/value pairs on the environment itself. These sit underneath everything, so they fill in rather than overrule: anything a template already set keeps its value. Uniquely, they reach every container, helpers included — an init container usually needs the same connection settings as the service it prepares.
- The template's own
environment:. - The environment service's overrides — main service only, the last word.
Values may also be instance placeholders, references into a git repository, or references to sibling services — all resolved at deploy time, in that order. Service references resolve last, so a consumer always receives the provider's final value: a database password the provider takes from git reaches every service that references it already resolved.
Injected files
An environment service can carry files written into its containers at deploy time, exactly like a template's own files: each names a container inside the unit, an absolute target path, and its content. One with the same target path as a template file replaces it — how an environment swaps in its own config file without editing the shared template. Because they are keyed by container, they reach a unit's helpers too, not just the main service.
Instance lifetime
Instance lifetime in the environment editor sets how long this environment's instances run
after their last deploy. It takes humane values — 7d, 12h, 90m. Empty inherits the
installation default of seven days; 0 means never expire. Over the API the field is
ttlSeconds on the environment update call, an integer number of seconds, where null inherits
and 0 is never.
A change applies from the next deploy or extend — instances already running keep the expiry they were stamped with. Cloning an environment copies its lifetime along with the rest of the definition. What happens when the time runs out is an instance matter: the instance is stopped, never deleted.
Cloning
Cloning copies an environment's definition under a new slug — services, image tags, environment-variable overrides, waves, injected files and shared variables. Instances are never copied; they stay with the original, and neither the blueprint flag nor protection is inherited.
This is how a second version stream is made: clone dev to dev-v2, then edit the tags. The
variation is named and visible in the environment list, instead of hidden in per-instance
parameters. In the dialog, typing a name suggests the slug; from a pipeline it is one call —
fadebox env clone dev dev-v2.
Blueprints
An environment can be marked a blueprint: a definition kept to be cloned from and never deployed. Creating an instance of one is refused, so a blueprint is the environment you can improve freely, knowing nothing is running from it.
The problem it solves shows up once a team clones regularly. The environment everyone copies —
call it base — is an ordinary environment, so it can also be deployed, and over time it collects
instances of its own. Now editing it to improve the next clone risks disturbing something live, and
the "canonical" environment is only canonical by convention. Marking it a blueprint makes that
convention a rule.
Three things follow from it, and the third is the one worth remembering:
- Instances are refused. The New instance button is gone in the UI; over the API and the CLI
the call answers
409with "… is a blueprint — clone it to deploy". - The flag can only be set while the environment has no instances, because that is exactly what it promises. If it still has some, the save is refused and tells you how many; delete them first. Clearing the flag has no such condition — a blueprint you decide you want to run just becomes an ordinary environment again.
- Clones are deployable. A clone does not inherit the flag, which is the whole point: cloning
is how a blueprint gets used.
fadebox env clone base devgives you adevyou can deploy immediately. When you genuinely want a second base — forkingbaseintobase-eu— the clone dialog offers a checkbox to make the copy a blueprint too, which is safer than cloning and then editing, since it leaves no moment where the copy is deployable.
Set it with the Blueprint checkbox in the environment editor, or blueprint: true on the
create, update and clone API calls. Blueprints carry a blueprint badge in the environment list
and on the environment page, and a blueprint value in the KIND column of fadebox env list.
Everything else about a blueprint is an ordinary environment: it holds services, image tags, shared variables and files, it can be exported as Compose like any other, and anyone who could edit an ordinary environment can edit it. A base the whole team clones from usually wants protection as well; the two flags answer different questions.
Protected environments
An environment can be marked protected: its definition may only be changed by a project
maintainer, even where composing is otherwise an
editor right. It is the guard for the stack a team
shares. The environment everyone deploys against — staging, uat — is the one where a retag
somebody makes for their own reasons surprises three colleagues on their next deploy, and
protection is how that stack stays in a few hands without keeping everyone else out of the
composer.
Four things follow:
- An editor's writes are refused. Services, waves, image tags, shared variables, injected
files, and deleting the environment: the call answers
403with the codeenvironment_protectedand a message saying to ask a maintainer. In the UI the composer's controls stay visible but disabled, carrying the same sentence. - Cloning is not refused. An editor may clone a protected environment and the clone is
unprotected — copy
staging, retag the copy, deploy it. Protection guards the original; it does not withhold what is in it. - Instances are untouched. Creating, deploying, stopping, extending and deleting instances of
a protected environment stay a
deployerright. Protection says who may change what the environment is, never whether it may run —demois exactly the environment a product owner should deploy and must not edit. - Only a maintainer raises or lowers it, in either direction and with no precondition. Unlike the blueprint flag it makes no claim about what is running, so an environment with instances can be protected exactly as it stands.
Set it from the environment's settings, or with PUT and DELETE on
/api/projects/{project}/environments/{slug}/protection. It is deliberately not a field on the
environment update call, so an ordinary edit cannot flip it by accident. Protected environments
carry a lock badge beside the name in the environment list and on the environment page.
Protection and the blueprint flag are separate questions — who may edit this? and can this be
deployed? — and all four combinations mean something: a protected blueprint is the team's
curated base, a protected deployable is staging, an unprotected blueprint is one person's
private base, and an unprotected deployable is one person's own stack.
Exporting
An environment can be exported as a standalone Docker Compose project, with the naming and
ordering fadebox would apply: plain YAML when it has no injected files, otherwise a zip of
docker-compose.yaml plus the bind-mounted files/. Useful for review, and for running the same
stack outside fadebox.
Service references are
resolved in the export: the environment's definition holds everything they need, so
{{service.db.host}} comes out as db and {{service.db.env.POSTGRES_PASSWORD}} as the actual
value — the exported file runs. What has no resolver outside fadebox — {{git:…}} image tags,
instance placeholders, and a service reference whose provider is missing or whose value is itself
such a placeholder — comes out verbatim, under a header comment saying to replace it with a
literal before running the file, never silently substituted with something the environment didn't
say. Output stays deterministic either way, which is what makes diffing an export against a
committed golden file a usable drift check.