Skip to main content
Version: 1.5

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.
PROVISIONINGOnly on an elastic runtime: the deploy has been accepted and a machine is being made for it. Nothing is deployed yet, and stopping cancels.
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.

On an elastic runtime the claim lands one step earlier, in PROVISIONING, because there is no host yet: a machine has to be created and booted first, which takes minutes. The deploy then follows by itself and the instance carries on through STARTING as usual. Stopping a PROVISIONING instance is instant — there are no containers — and deleting one is refused until it is stopped, as from any other busy status. See PROVISIONING. While it waits, the card says how far the machine has got under Provisioning machine… — waiting for its Docker daemon, starting the ingress router — and the whole story is the machine log.

Such an instance also names the size its machine was built at beside its runtime, and carries a machine chip on the line below saying what the machine is doing while it is anything but up — creating machine, machine booting, machine stopped · disk kept. Three of its readings are amber and are about cost or data rather than a deploy: machine not stopping, kept after a failure, and the countdown on a stopped disk the runtime is about to give up — disk kept 5d 4h more, then disk expired 2d ago once it has. See the instance and its machine.

Only DRAFT and STOPPED instances can be deleted, or moved to another runtime. An ERROR instance is both deployable and stoppable, so stop-then-delete is always available; you can never be stranded.

What the card offers is one action and a menu. The instance's state picks the button — Deploy on a DRAFT or STOPPED instance, Redeploy on one that is RUNNING with an edited environment or in ERROR, Stop while it runs, Cancel while it is provisioning or starting — and everything else is behind More: Deploy or Stop when the state did not lead with it, Reset, Extend, Describe, Edit overrides, Move, Approve and Reject, sharing, Access, and Delete instance last. Nothing is hidden from anybody: an action this caller may not use stays in the menu, greyed out and naming whom to ask. The status panel lays the same set out as a row of buttons, for the surface that has the width for it.

Deploying an instance that is already running converges it: the services that changed are restarted and the rest keep running — see Redeploy below. A service that is restarted has its containers replaced, so there is no rolling replacement within it. Named volumes and the instance network survive a deploy, and the URLs do not change. (Stopping removes the containers and the network but keeps the volumes; the only thing that removes an instance's volumes is Reset below, so an ordinary stop or delete leaves them on the host — 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.

Redeploy restarts only what changed​

A redeploy does not tear the stack down and build it again. It compares what is running against what the environment now resolves to, container by container, and touches the difference:

OutcomeWhen
createdNothing is running for it — a first deploy, or a service added since the last one.
recreatedIts configuration differs from the resolved definition, the image moved under its tag when it was pulled, or Fadebox would not call it healthy at this moment.
keptNone of those: same configuration, same image, up and ready.
removedNothing in the environment names it any more — a service renamed, or dropped.

So bumping one service's tag restarts that service and leaves the Postgres, the Keycloak and everything else alone, which is what docker compose up -d does and what people expect a redeploy to mean.

Three rules decide what comes back up with what:

  • A service is a unit. Its primary container, its init helpers and its sidecars restart together — the unit is kept only when every container of it is kept. That is what stops a stale sidecar surviving beside a fresh main container, and it is why an init job re-runs when its own service does rather than on every deploy.
  • Nothing cascades. Recreating a wave-0 database does not recreate the wave-1 application that talks to it; a dependent is expected to reconnect, which is what a start order already assumes of it.
  • Images are pulled before anything is touched. A tag that does not exist, or a registry login that is missing, fails the deploy with the instance still running — the same way a failed git value read does. Nothing is left half torn down.

Reset is the other behaviour, and it is unchanged: it replaces everything the instance owns, volumes included.

Every attempt records what it did per container, so a row of the deploy history reads restarted backend · kept 4 and the attempt's page says it per service. Containers created by an older Fadebox carry no configuration stamp and are therefore recreated once, on the first redeploy after the upgrade.

Runtime​

An instance runs on one runtime, chosen when it is created and shown on its card. The Runtime field is always offered, however many runtimes the project has, because the set changes: a runtime can be disabled tomorrow, and an instance created against it stays where it is. The field is pre-filled with what the server would pick for a request that names none — local when it is enabled and available to the project, otherwise the project's only enabled runtime — and left empty when several are enabled and none is local: that case is a real choice, since the runtime decides the instance's URLs and which daemon holds its volumes, so the server refuses to guess (409 runtime_unavailable, naming the candidates) rather than pick one.

A disabled runtime blocks deploys, not the instance. An instance whose runtime has been switched off shows it on its card, and Deploy and Reset are greyed out with the reason; over the API or the CLI either one is refused with 409 runtime_unavailable, naming the enabled runtimes the project could use instead. The way out is Move, under the card's More menu: a deployer can move a DRAFT or STOPPED instance to any runtime that is enabled and available to the project — there, or fadebox instance move <slug> --runtime <runtime>. The same two states that allow a delete, for the same reason: the instance's row is the only record of which containers, networks and volumes are its, so it cannot be re-pointed while any of them exist. Stop it first. The move is recorded in the audit log as instance.runtime.changed.

A move is not a migration. The instance keeps its slug, its name, its overrides and its history — not its data. Volumes belong to the daemon they were created on and nothing copies them, and each runtime carries its own ingress domain, so the next deploy comes up on new URLs and empty. What the old runtime still holds shows up under its Resources on host as orphans, for an admin to reclaim.

Overrides: what a deployer may change​

By default an instance is a name and a runtime, and everything else comes from the definition. An environment can open individual services for variables, for the image tag, or both — and on those services, and only those, whoever deploys sets their own values.

fadebox instance create pr-1842 \
--tag backend=2.7.0-rc3 \
--var backend:SPRING_PROFILES_ACTIVE=test \
--var postgres:SEED_DATASET=expired

Set them at creation or afterwards; either way they take effect on the next deploy, not on the spot. fadebox instance set <slug> --redeploy — and Save and redeploy in the UI — is the one step that does both, because editing a log level and redeploying is the loop this exists for.

Where an override lands. A variable override applies to every container of that service's unit, helpers included — a SEED_DATASET is usually consumed by the init container that seeds the database, and an override is a knob of the service, not of one container. That is deliberately wider than the environment's own service variables, which reach the main container only. Full precedence, lowest to highest:

VariablesImage tag
1Environment's shared variablesTemplate
2Template's own environment:Environment service row
3Environment service row (main container only)This instance's override
4This instance's override (whole unit)

Values are literal. A {{git:…}} reference is refused — values are literal — so an override can never point the git resolver somewhere of the deployer's choosing. "Latest test build" stays what it already is, a reference on the service row; a deployer who wants it leaves the tag alone. A tag override is held to the same grammar as the row's, so it can only ever select another tag of the template's own repository.

Refusals are 400s that name the service: setting a variable on a service that is not open comes back as backend does not accept variable overrides. Variable names match ^[A-Z_][A-Z0-9_]*$, at most 50 per service, values single-line and at most 200 characters. The variables field flags a {{ and a name outside that pattern as you type, before anything is sent.

Overrides are visible, so they are not for secrets. Every project viewer sees them on the instance card and in the audit log detail. A value that must not be read belongs on the environment, where a maintainer keeps it.

If a service is closed again, or removed, the values you set stay stored but stop being applied, and the card marks them no longer applied rather than hiding them — an instance that silently dropped an override would be claiming to run something it does not. Reopen the service and they apply again.

Reset​

Reset removes everything the instance owns — containers, network and volumes — and deploys it again with the same overrides. The instance keeps its slug, its namespace, its URLs and its expiry policy; what it loses is state.

That is the difference from a redeploy, which keeps the volumes. Reset is the answer to let me start over: the database goes back to whatever the deploy puts there, and the URL you shared still works.

Available to a deployer from RUNNING, ERROR and STOPPED — Reset under the card's More menu, behind a confirmation (--yes in the CLI, which a non-interactive shell must pass). There is no separate status — the instance is STARTING while it resets, and its history reads reset requested → deploy finished.

Volumes that are not the instance's are never touched: a bind mount or a host path belongs to whoever set it up.

What a copy is for​

An instance knows what it runs. What it cannot work out for itself is why somebody made it — which pull request, which customer demo, which bug. So an instance carries a purpose, one line under its name, and up to eight links, each a label and an http/https URL: the merge request, the ticket, the design doc.

fadebox instance up pr-1842 \
--purpose "Discount codes, PR #842" \
--link "PR #842=https://gitlab.example.com/shop/-/merge_requests/842" \
--link "SHOP-1291=https://jira.example.com/browse/SHOP-1291"

Set them when the instance is created — the New Instance sheet has both fields, and so do instance create and instance up — or afterwards, from Describe… under the card's More menu or fadebox instance describe <slug>. Setting them is a deployer act, like the rest of the instance lifecycle.

They describe the instance, not what it is running, so they survive every redeploy. The pull request is the same pull request after the fourth push, and no deploy, reset or stop clears them. That is why they sit on the instance, while checks and the verdict — statements about a content — sit on the deploy attempt instead.

Writing them replaces both together: a call that sends no purpose clears the purpose, and one that sends no links clears the links, the same full-replace rule as overrides. The CLI softens the sharp edge where it can — give instance describe only --link and it carries the existing purpose over, so adding a link never wipes the line — and --clear removes both.

A purpose is at most 200 characters on a single line; a link label at most 40. Control characters and bidirectional overrides are refused (400 purpose_invalid), because fadebox instance list prints the purpose into a terminal — it has a PURPOSE column between SLUG and STATUS, cut at 40 characters — and a line that renders as something other than what it says is worse than no line.

Checks and the verdict​

Two more facts, and they answer is it fit rather than what is it: a check is a pipeline's statement about a suite it ran, and a verdict is a person's yes or no. Both are written on the instance's running deploy attempt — the newest row of Deploy history — and never on the instance itself.

That placement is the whole design. A green end-to-end run and an approved were about the content that was up at the time, so a redeploy starts a new row with no checks and no verdict, and the card says so. Nothing is cleared and nothing is copied: the previous attempt keeps both, which is what answers the tester who says but it passed yesterday.

Checks​

A pipeline reports a named result, and repeats it as the run progresses:

fadebox instance check pr-1842 e2e --state pending
# … run the suite …
fadebox instance check pr-1842 e2e --state passed \
--summary "84 passed, 0 failed" --run-url "$CI_JOB_URL"
  • The name is a lowercase slug starting with a letter, at most 40 characters — build, integration, e2e, smoke. Reporting the same name again replaces it, which is exactly what lets one suite say pending when it starts and passed or failed when it ends.
  • The state is pending, passed or failed. The one-line summary and the link to the run are optional; --run-url is spelled that way because --url is already the CLI's own server address.
  • Who reported it and when are the server's, taken from the credential that made the call and never read out of the request body, so a check cannot be made to name somebody else.
  • At most 20 checks on one attempt, and --remove deletes one.

The verdict​

A person says yes or no to what is running, once per deploy attempt:

fadebox instance approve pr-1842 --note "Discount totals correct on all three tiers"
fadebox instance reject pr-1842 --note "Stacked coupons still give the wrong total"

A note is optional on an approval and required on a rejection — a no without a reason is the thing the ticket comment would have said anyway, and the point of writing it here is that it sits beside the content it is about. Up to 500 characters, and it may span lines. A second verdict replaces the first, with the audit log keeping both; fadebox instance verdict <slug> --withdraw takes it back, and the same command with no flag prints what stands.

Approving and rejecting are deployer acts — the rung a tester and a product owner already hold, which is the rung that brings a stack up and says whether it is right.

A verdict is not a lifecycle state. It does not move the instance's status or health, and it is not a reason to keep an instance alive: an approved instance expires like any other. If it should outlive its TTL, extend it.

When a check or a verdict is refused​

Both are accepted only while the instance is RUNNING and its newest deploy attempt ended RUNNING. Otherwise the answer is 409 instance_state — it has no running deploy to check. That closes the window a redeploy opens: a check arriving while the stack is being replaced would otherwise be recorded against content that is already on its way out, and failing the pipeline step is the better of the two. A malformed check is 400 check_invalid, a malformed verdict 400 verdict_invalid.

Where they show up​

On the instance card, checks are badges and the verdict a chip beside them (Approved by alice), and Approve and Reject are the first two items of the card's More menu while the instance is RUNNING. The status panel has a Checks section with each summary and a link to the run, and a Verdict block with the note and who left it. Every row of the deploy history carries the checks and the verdict of that attempt, and the attempt's page shows them in full — which is the history the placement rule exists to give you.

An agent can set a verdict (set_instance_verdict) but cannot report a check. That is deliberate, and the reasoning is on the MCP page.

Deploy history​

Every deploy and reset writes a row saying what it produced: when it started and finished, whether it was a deploy or a reset, how it ended, the image each service actually ran after every resolution pass, the override values applied, what the attempt did to each container — created, recreated, kept or removed — and, once it is up, the checks and the verdict left on it. The last 20 attempts per instance are kept, and GET …/instances/{slug}/deploys lists them, newest first, for any project viewer.

This is what makes an instance card truthful once a tag is a moving git reference: latest test build says nothing a week later, while the record says which image that was on the day.

The instance's history​

The card's history chip — and Full history in the status panel, whose Deploys section keeps one line per attempt — opens the instance's history: one row per kept attempt, newest first. Under each row is what changed since the attempt before it — web :1.4.0 → :1.4.2, web LOG_LEVEL debug → info, gitops@master a3f91c2 → 9b1e0d4 — then what the attempt did to the containers (restarted web · kept 3), and the checks and verdict of that attempt. Pick two rows and Compare opens the newer against the older.

One attempt, whole​

Every row is a page. It joins, per service, the four things the record keeps about it: the image that ran, the repository, ref and commit it came from, the override values as they were applied — not as the instance has them now, which a deployer may have edited since — and whether its container was created, restarted, kept or removed. Above the services: the error as it was recorded, each check with its summary and link, the verdict with its whole note, and the download of the log its containers left when a stop or reset removed them.

Changed since leads the page. By default it is the difference against the previous kept attempt, whatever that one's outcome — after a failure, what did I change that broke it is the question. The picker offers every other kept attempt, and the choice goes into the URL (…/deploys/201?against=151), so a comparison can be pasted into a ticket. Compared are the images, the override tags and variables, and the commit each git reference resolved to; a service that arrived or left says just that. Container outcomes, checks and verdicts are not compared — they are statements about an attempt, not state it carried. The difference is computed from the two records when the page is shown; nothing is stored.

The environment's history​

History on the environment page lists every kept attempt of every instance the environment has, newest first and paged, with the same columns — instance, when, deploy or reset, outcome and duration, images, containers, checks — and filters by instance and by outcome, where in progress selects the attempts that have no outcome yet. Each row opens its attempt. Over the API, GET …/environments/{envName}/deploys takes page, size and sort (startedAt, instance, outcome, kind), a repeatable instance (slugs) and a repeatable outcome (RUNNING, ERROR, CANCELLED, or PENDING for in flight; anything else is a 400), and answers {instance: {slug, name}, attempt: …} per row, the attempt in the same shape as the instance's own listing minus the log capture. A deleted instance's attempts go with it; the audit log is what remembers a deleted thing.

Which commit a git reference read​

Every {{git:…}} reference the environment uses resolves to a commit when the instance deploys, and the attempt records what it resolved to: per repository and ref, the commit it answered from and which services read it. So the card reads gitops · a7c48fe on master, and last Tuesday's attempt still names the commit it ran, long after the branch has moved on.

There is nothing to set and nothing extra is fetched — the deploy already had the answer. This is the half of why this copy exists that can be derived, which is exactly why purpose and links are for what git cannot know: the pull request, the ticket, the customer.

The commit is a link to the forge's commit page when the repository's remote is on a host whose URL shape is known — github.com, gitlab.com or codeberg.org, over https. Everywhere else — a self-hosted GitLab, a Gitea, an ssh:// remote — the text stands on its own. A self-hosted forge cannot be told from any other host by looking at its URL, and a link that lands somewhere wrong is worse than no link.

The record stores the repository's slug, never its remote URL, and the link is rebuilt from the remote's host and path at the moment the page is rendered. A credential written into a remote URL therefore never reaches the record or the page, and a viewer who may read the deploy history does not learn from it where the remote lives.

fadebox instance get prints the same under References:, and fadebox instance status as a table of repository, ref, commit and the services that read it.

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 — an item under the card's More menu 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.

On an elastic runtime the same thing happens against the instance's own machine: the sweep and the event stream both reach each machine separately, so an instance there is read as closely as one on a shared host. An instance whose machine is not ready shows UNKNOWN and says which case it is — it has no machine yet, or the machine is in some state other than ready — instead of leaving an old verdict standing for a host that is not answering. Opening its status panel reads the same UNKNOWN: with no daemon to ask there is nothing to call DOWN, so an instance whose failed deploy switched its machine off is not shown as down, and its audit log gains no instance.health_changed rows from the panel and the sweep disagreeing.

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.

What a container is costing​

Each container on an instance's status shows the CPU it is using and its memory against its limit — 0.42 cores · 312 MiB / 2 GiB. A service that set no memory limit shows the host's total as its ceiling, so the figure reads as used of host rather than of a limit it does not have. Below the container list, Volumes is what this instance's managed volumes take on the host, which is the number to look at when a runtime's disk is filling.

These come from the same periodic reading as the runtime's own bars, not from a fresh call to Docker for this page — which is why the status view can keep refreshing without asking the daemon anything. The same rules apply: a dash means nothing has been observed yet rather than nothing is being used, CPU needs two readings before it says anything, and the volume figure comes from the slower disk pass.

An instance on an elastic runtime shows the same per-container figures — the reading reaches each machine separately, and a container carries the same labels there as on a host. Volumes is the exception: the slower disk pass does not visit machines, so it reads as a dash, as do the runtime's own bars, which describe a host such a runtime does not have.

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.

Any project viewer may read logs and download the export; each download is recorded in the audit log.

On an elastic runtime the status panel's log pane also lists an entry named machine: what Fadebox said while it made, started or stopped the instance's machine — the machine log. It is readable while the instance is PROVISIONING, when there are no containers yet, and it is not part of the download.

Logs outlive a stop​

A stop and a reset remove the instance's containers, and a container's log would go with them — worst for exactly the instance that failed, was stopped to free the runtime, and is now the subject of what did it actually say. So both read every container's log before removing it and keep the capture beside the deploy attempt it belongs to: one file per service, and a manifest saying what was captured and what was not.

Nothing about reading changes. When the instance has no running container, the same tail and the same Download serve the capture instead, and say so: the status panel marks the Logs section captured at the stop's time, and over the API two response headers name the source —

X-Fadebox-Log-Source: live | capture
X-Fadebox-Log-Captured-At: 2026-09-14T09:12:03Z

— so the body stays the log verbatim, with nothing Fadebox invented in it. The CLI's fadebox instance logs prints a one-line note on stderr when it shows a capture, and its stdout is still only the log. A downloaded capture carries "source": "capture", the attempt and the time in its manifest.json.

Each attempt keeps its own capture, so an attempt's page in the deploy history offers a download of the log its containers left even after later attempts, from

GET …/instances/{slug}/deploys/{id}/logs/export

A capture is scoped and capped exactly as an export is — the default tail, the same byte caps and the same truncation marker — which is also why the export's scoping parameters (tail, since, until, timestamps) are refused with a 400 on a capture: the file is already scoped, and re-scoping it silently would make one parameter mean two things. service still filters. A capture is bounded in time as well as bytes, so a runtime that stops answering delays the stop by at most the read timeout and costs that service its capture, never the stop itself; a capture that could not be written is recorded in the manifest and the stop completes regardless.

What is not captured: a redeploy. It removes containers too, but what it leaves behind is a running instance whose live logs work, so export first if you are still diagnosing the service a redeploy is about to replace. Captures live on the server's disk under a configurable directory, kept as long as their deploy attempt — the last 20 per instance — and removed with the instance, its environment or its project. Losing that directory is harmless: every reader simply says there is no capture.

Who can open its URLs​

An instance's Access says who may open its ingress URLs: Open — anyone who can reach them; Members — the project's members and administrators, after a Fadebox sign-in; or Creator — its creator while a member of the project, the project's editors and administrators. The level is the instance's own: Access under the card's More menu, fadebox instance gate / ungate, or gate on the create call. What a new instance starts at is its environment's default. Raising it is for whoever may operate the instance; lowering it takes editor.

Opening or closing the gate applies on the next deploy, and until then the card says what is actually in force before what was asked for: open, members on next deploy means the URLs are still open. A move between Members and Creator applies to the next sign-in at once. The whole flow — what a visitor sees, how a pipeline gets through, what the installation must have set up — is under Requiring a sign-in. Every change writes instance.gate_changed to the audit log, with the level it ended on.

This is a separate question from ownership below, which is about who may operate the instance, and from a protected environment, which is about who may edit the definition.

Reaching its services from your own tools​

An instance of an environment with native access on carries a gateway into the installation's tailnet, and each of its services a fixed address there — so psql, DBeaver or redis-cli on a developer's laptop connect to 10.224.0.3:5432 directly, with no port published on the host. The drawer's Connect section shows the gateway's state and a row per service with its address, ports and a copy button; fadebox instance connect prints the same. The route is the tailnet's policy to grant: the instance's access level and its sharing do not apply on the tailnet.

Without a tailnet, the instance agent Fadebox runs in every instance does the same through Fadebox itself: fadebox connect pr-42 tunnels each service to localhost on your machine, and here the instance's access level does apply — every connection is checked against the project role and the gate, and audited.

Ownership​

Every instance names who created it — by alice on the card, with a mail link when the installation shows email addresses (below). What that name lets anyone do depends on whether the instance is shared or personal.

A shared instance is the project's: any deployer may deploy, stop, extend, reset, move or delete it, set its overrides, describe it and report on it. That is what lets a pipeline's cleanup job tear down what a different job started. A personal instance is its creator's: only they, the project's editors and maintainers, and a global admin may do those things. Everyone else in the project still sees it — the card, its status, its logs, its history — and the controls they may not use are disabled with the reason, naming whom to ask. The API refuses the same calls with 403 instance_personal, carrying the creator's username.

Which one an instance starts as is the environment's call: each environment has a New instances are shared switch, described under shared or personal instances. A new environment starts with it off, so its instances are personal unless the editor says otherwise; every environment that existed before this feature keeps producing shared ones, so nothing changed on upgrade. Whoever creates an instance can choose the other on the create form (--shared or --personal in the CLI), and the creator or an editor can share or unshare it later, in either direction — Share with the project and Make personal under the card's More menu. Other deployers cannot, whichever way it currently points. Every change writes instance.sharing_changed to the audit log.

The line was drawn at editor rather than maintainer on purpose: whoever composes the environment operates everything running from it. A trainer resets any attendee's lab, and the developers stop any instance CI made — which matters, because a service account is a creator like anyone else. A per-pull-request pipeline's instances are personal to that account unless its environment says new instances are shared, so a cleanup job that runs as a different account needs editor, or the switch on the environment. The expiry sweep stops a personal instance like any other.

If the creator's account is removed, a personal instance is left to the editors, who can share it with one click; nobody inherits it by accident.

The creator's email address. By default the name on the card links to the creator's email, so a colleague blocked by a personal instance can reach them. An administrator can switch that off under Administration → System (Show instance creators' email addresses) for an installation that runs across contractors or partners. The name stays on the card either way, and the switch writes system.creator_email_changed to the audit log.