Skip to main content
Version: 1.0

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
StateMeaning
DRAFTBeing configured; not yet deployed.
STOPPEDDefined, nothing running.
STARTINGA deploy is in progress.
RUNNINGEvery service is up and ready — healthy where it declares a healthcheck, merely started where it does not.
STOPPINGTear-down in progress.
ERRORThe 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.

HealthMeaning
HEALTHYEvery container is running — and healthy, where it has a healthcheck; every run-once job has exited 0.
DEGRADEDSomething is up and something is not: a service crashed while its siblings run, or a healthcheck is failing.
DOWNNothing is running — every container exited, or none are left.
UNKNOWNFadebox 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 UNKNOWN until 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"
ParameterDefaultMeaning
serviceall containersRepeatable; takes the service keys the status panel shows.
tail100000Last N lines per container, or all.
since / until(unset)ISO-8601 instants; the Docker API applies them at second granularity.
timestampstrueThe 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.