Instances
An instance is a running copy of an environment on a chosen
runtime. One environment can have many: pr-42, pr-43, alice, demo — all from
the same definition, all at the same time, even on the same Docker host.
The one environment that cannot have instances is a blueprint, which exists to be cloned rather than run; clone it and deploy the clone.
TeamEnterprise
Free runs five at once per runtime; redeploying one that is already running never counts against that. See Licensing.
Isolation
Every instance gets a namespace, {project}-{environment}-{instance}, and everything it
creates is prefixed with it: container names, the bridge network, named volumes. That is what lets
pr-42 and pr-43 of the same environment coexist on one daemon without colliding, and what makes
teardown exact — stopping an instance removes what carries its labels and nothing else.
The instance name you type becomes a slug. If that slug is taken within the environment, fadebox
disambiguates it (pr-123 → pr-123-2) rather than failing — convenient in the UI, worth knowing
about in a pipeline, where the CLI's instance up does find-or-create instead
so a retried job does not accumulate copies.
One hostname, one instance. A service reachable through ingress is
published at {namespace}-{service}.{domain}, and no two live instances may answer on the same
name — including instances in different projects, since dashes make two different namespaces
capable of joining to one hostname. A deploy that would claim a name another instance already
serves is refused:
HTTP 409
{
"error": "ingress_host_taken",
"message": "Ingress host 'shop-dev-pr42-web.envs.example.com' is already served by instance 'shop-dev-pr42' — rename the service or the instance, or stop that instance first",
"args": { "host": "shop-dev-pr42-web.envs.example.com", "heldBy": "shop-dev-pr42" }
}
heldBy is the holder's namespace, which reads as {project}-{environment}-{instance}, so it
says which instance to stop — or, if it is not yours to stop, what to rename. An instance holds
its hostnames from the moment a deploy is claimed until it is stopped: one that failed keeps
them, because its containers may still be running and still answering.
Lifecycle
DRAFT ──► STOPPED ──► STARTING ──► RUNNING ──► STOPPING ──► STOPPED
│ │ │
│ (cancel) └────────► ERROR ◄───────┘
└─────────► STOPPING
| State | Meaning |
|---|---|
DRAFT | Being configured; not yet deployed. |
STOPPED | Defined, nothing running. |
STARTING | A deploy is in progress. |
RUNNING | Every service is up and ready — healthy where it declares a healthcheck, merely started where it does not. |
STOPPING | Tear-down in progress. |
ERROR | The last deploy or stop attempt failed. |
Deploy is asynchronous. The call returns immediately with STARTING and the work continues on
a worker; poll the status endpoint (or watch the UI) to see it settle. Stopping mid-deploy
cancels it rather than being refused — STARTING → STOPPING is a supported edge.
Only DRAFT and STOPPED instances can be deleted. An ERROR instance is both deployable and
stoppable, so stop-then-delete is always available; you can never be stranded.
Deploying an instance that is already running replaces it whole. Every container of the previous deploy is removed first — helpers included, and services that have since been renamed or removed from the environment along with them — and only then does the new generation start. There is no rolling replacement, so the instance is down for the length of the deploy, image pulls included. Named volumes and the instance network survive it, and the URLs do not change. (Stopping removes the containers and the network; named volumes are removed by no path at all, so they outlive even a deleted instance — see reclaiming host resources.) Values resolve before anything is torn down, so a deploy that fails while reading a git value leaves what is running untouched.
Expiry
Instances are meant to be disposable, so they carry a lifetime. Every deploy stamps the instance with an expiry — now plus the environment's TTL, seven days unless the installation says otherwise — and a sweep stops whatever has run out.
Expiry stops; it never deletes. A swept instance lands in STOPPED with its definition
untouched, and a redeploy brings it back and starts the clock again. This collects abandoned
containers, not anyone's work.
Two things reset the clock: every deploy and redeploy, so an instance in active use never
expires under its user, and Extend — a button on the instance that pushes the expiry to a
full TTL from now without redeploying. Extend needs the deployer role and appears only while
the instance is running or in ERROR and actually has an expiry. Creating an instance does not
start the clock: a DRAFT has none until its first deploy.
RUNNING and ERROR instances are swept — ERROR deliberately, since a failed deploy leaves
containers holding a network, which is exactly the litter this exists to collect. A deploy in
flight is never swept, and an Extend or redeploy that lands while the sweep is deciding wins.
The sweep runs every minute by default, so a stop follows the expiry closely rather than to the
second.
The instance shows its remaining time as a countdown (3d 4h, then now) with the exact
timestamp in the tooltip; one that ran out and was stopped is badged expired, and
fadebox instance get prints the same as Expires:. Whether a person or the sweep stopped
something is answered by the instance's history, where a swept stop is recorded by the
instance-expiry system actor — see the audit log.
The lifetime itself belongs to the environment: see instance lifetime.
Status is what fadebox did; health is what the daemon says
An instance carries two signals, and they answer different questions.
Status is the lifecycle above — RUNNING, ERROR, and the rest. It records what fadebox last
did to the instance and whether it finished: RUNNING means a deploy brought every service up
and saw it ready. Fadebox does not then keep watching. Once a deploy lands RUNNING, that stays
until you stop or redeploy — whatever later happens to the containers.
Health is what the Docker daemon says about those containers now. Fadebox keeps no shadow
copy of what is running — per-container state comes from the daemon through the instance's labels —
so a container stopped by hand, crashed, OOM-killed or gone unhealthy shows up in the health even
while the status still reads RUNNING.
| Health | Meaning |
|---|---|
HEALTHY | Every container is running — and healthy, where it has a healthcheck; every run-once job has exited 0. |
DEGRADED | Something is up and something is not: a service crashed while its siblings run, or a healthcheck is failing. |
DOWN | Nothing is running — every container exited, or none are left. |
UNKNOWN | Fadebox has not looked yet, or the runtime is unreachable. |
On the instance card the status is the badge; the health is the coloured dot beside it — teal
healthy, amber degraded, red down, grey unknown — and an unhealthy instance shows the reason
(web exited (137)) and when it was last looked at. Health is shown only for a RUNNING or
ERROR instance; a stopped one has no containers to have an opinion about.
Fadebox keeps health current mostly on its own: it watches the daemon's event stream, so a
container that dies, crashes or turns unhealthy is usually reflected within a second or two. A
periodic sweep (every five minutes by default) backs the stream up — it catches what an event
can't tell you, such as a container that died while fadebox was down — and opening an instance's
status panel refreshes it on the spot. An admin can force a whole runtime from Administration →
Runtimes → the runtime → Resources on host → Refresh
instances. A change of verdict is recorded in the audit log as
instance.health_changed.
Health is observed, never acted on: fadebox tells you a container is down, it does not restart
it — that is what a Compose restart: policy is for. To bring a
broken instance back, redeploy it (rebuilds from the definition) or stop it.
Reading the daemon rather than a shadow copy has two consequences worth relying on:
- Running instances survive a fadebox restart. Containers are not tied to the app's lifetime;
after a restart the health reads
UNKNOWNuntil the next refresh, then catches up. - What the status panel shows is what exists. Per-container state, health and logs come from the daemon at the moment you ask.
If the app is killed mid-deploy, the instance is left claiming a worker that no longer exists. On
the next startup such instances are swept to ERROR — deliberately not to a state claiming to know
what is on the host, because that question is answered by the health.
Logs
The status panel shows a per-service tail — a snapshot of one service's output (capped at 5000 lines), not a stream. The UI and the CLI both read the same endpoint. Both read what the daemon still holds — Container logs is the cap that decides how far back that is.
Next to the tail sits Download, which streams the whole instance's logs as one zip: a
logs/<service>.log entry per container — init helpers and sidecars included — plus a
manifest.json recording what was exported (image, state, exit code, bytes, and whether the entry
was cut short, per container). Lines carry the daemon's timestamps, so output from different
containers can be ordered.
The download URL takes scoping parameters, which is also how a pipeline uses it with an API key:
curl -sf -OJ -H "Authorization: Bearer $FADEBOX_API_KEY" \
"$HOST/api/projects/$P/environments/$E/instances/$I/logs/export?since=2026-08-16T12:00:00Z"
| Parameter | Default | Meaning |
|---|---|---|
service | all containers | Repeatable; takes the service keys the status panel shows. |
tail | 100000 | Last N lines per container, or all. |
since / until | (unset) | ISO-8601 instants; the Docker API applies them at second granularity. |
timestamps | true | The per-line timestamp prefix. |
Docker cannot report a log's size in advance, so the export is bounded rather than measured:
the default tail keeps each container's recent end, and byte caps
(configurable) cut a pathological entry mid-stream
with a visible [fadebox] truncated … marker rather than failing the download — the zip is
always valid, and the manifest says exactly what was cut.
Both the tail and the export read live containers, and container logs die with the containers —
on a stop, and on the redeploy that replaces them. Export before you stop or redeploy
something you are still diagnosing. Any project viewer may read
logs and download the export; each download is recorded in the
audit log.
Ownership
The creator of an instance is recorded and displayed, but it is not an access control: any
deployer in the project may deploy, stop or delete any instance in it. That is what lets a
pipeline's cleanup job tear down what a different job started.