Audit log
Enterprise
Who did what, when — state-changing and security-relevant actions recorded as structured, append-only events: actor, action, target, outcome, timestamp, and the context that makes a row useful on its own. Recording is an Enterprise feature (Licensing); reading is never gated, so history recorded while entitled stays visible on every tier.
Where to read it
| Place | Who | What it shows |
|---|---|---|
| Administration → Audit Log | global admin | Every event in the installation, filterable. |
| Project → Settings → Audit Log | project maintainer | The project's own slice — the same view, scoped server-side. |
| The instance's timeline in its status panel | project viewer | One instance's events, newest first: who created, deployed and stopped it, and how each attempt ended. |
Filters narrow by actor, action, outcome, target and a time window, and free-text search matches
actor names, target names and projects. The same queries are available over the API
(GET /api/admin/audit, GET /api/projects/{project}/audit) for scripting.
Times are yours, storage is UTC
Every timestamp is stored in UTC and shown in your browser's own time zone and date
format, so the same event reads 4:04 PM for a colleague in Prague and 7:04 AM for one in
California. Picking a day in the time-range filter means that day where you are, matching the
column beside it — so a window never hides a row that the listing would have shown.
The clock follows Settings → Appearance → Time format: Automatic takes its cue from your browser's locale, or you can pin 24-hour or 12-hour. That setting governs every timestamp Fadebox renders, not just this page.
Over the API a timestamp is an ISO-8601 instant with its offset spelled out —
2026-08-16T09:30:00Z — so a script can read it without knowing anything about the server. A
query window's from and to take the same form; the window is half-open — from is inclusive,
to exclusive — so back-to-back day windows tile without dropping or double-counting an event on
the boundary. A value can be copied straight out of a response and sent back as a bound. A bare wall-clock time
(2026-08-16T09:30:00) is refused with a 400, because without an offset it names no single
moment — which is exactly the ambiguity the rest of this section exists to remove. Convert a
hand-written local time to an instant first, the way the UI does when you pick a day.
What is recorded
Sign-ins — including failed ones, each with its reason, and the moment an account
locks after too many of them (user.login_locked, with
the count and the deadline; a request refused by the per-address throttle writes nothing, so a
flood cannot inflate the log) — and everything that changes state: users, groups and
membership; API keys; identity providers; the license; projects, environments and templates; the
whole instance lifecycle (create, deploy, stop, delete, extend,
move,
expiry sweeps, and whether it is
shared or personal — instance.sharing_changed) including
log exports and the log captured
when a stop or reset removes its containers (instance.logs_captured, with the attempt, the
byte count and which services could not be read), and every change in an instance's
health
(instance.health_changed, attributed to SYSTEM); what a person or a pipeline says about an
instance — its purpose and links
(instance.purpose_updated), the checks a pipeline reports
(instance.check_reported, instance.check_removed) and the
verdict somebody reaches (instance.verdict_set,
instance.verdict_withdrawn), each naming the deploy attempt it landed on, so a replaced check or
a second verdict leaves the first one readable; every
access token minted for a gated instance
(instance.access_token_issued, recording which hostnames it covers and never the value);
runtimes and their certificates; ingress
installations and the system-image registry
(system.image_registry_changed, carrying the new prefix or that it was reset); the
Prometheus exporter's switch (system.metrics_export_changed) and the one that
shows instance creators' email addresses
(system.creator_email_changed); every
image reclaim on a runtime
(runtime.images_reclaimed, with the count and the bytes freed, whether an admin, a project's
deployer or the retention timer did it); and fadebox's own cleanup jobs.
Connecting an agent is recorded too. Approving a consent request on the
MCP endpoint records oauth.grant_approved against the user who
approved it — the moment a machine credential comes into existence, and the answer to "who
connected this agent, and when". A token that fails to verify records oauth.auth_failed, the
way a failed sign-in does, naming the credential's public id rather than a user: a token that
does not verify names nobody.
Each event carries the actor (a user, a service account — with the API key
that authenticated the request — or SYSTEM for fadebox's own jobs), the action, the target and
its project, SUCCESS or FAILURE, when it happened, a source IP where the request carries
one, and action-specific detail: the deploy's error, the export's byte count, the role a
membership granted.
On a rejected credential the address is the record. A failed sign-in, a bad API key or an
access token that does not verify names no user, so where it came from — and how often — is what
the row is for. Behind a reverse proxy that address is the caller's only if the proxy forwards
it: with no X-Forwarded-For every event carries the proxy's own address, the same value for
every user. See Reverse proxy.
Three attribution rules worth knowing:
- Async outcomes belong to whoever asked. A deploy finishes on a worker thread, but its outcome event names the user who requested it — "who deployed this" returns a person on the outcome row too, never a pool thread.
- Fadebox's own jobs are
SYSTEMactors named after the job (instance-expiry,startup-sweep,retention), never an invented user. - An event exists only if the change actually happened. Rolled-back work records nothing, and failures — a failed login, a failed deploy — are recorded where the failure became final.
Recording follows the license
Below Enterprise the recorder simply drops events — the feature does not exist there — but the pages and the API stay open, showing whatever was recorded while entitled. A downgrade stops new events without hiding or deleting history: there are no carve-outs to non-destructive downgrades.
Shipping to a SIEM
Setting FADEBOX_AUDIT_LOG_MIRROR=true additionally emits every recorded event as one JSON
line on the fadebox.audit logger, so an existing log pipeline can forward events without
touching the database. The mirror is a feed, not the record — the database stays authoritative.
Retention
Events are kept forever by default. Setting FADEBOX_AUDIT_RETENTION (ISO-8601, e.g.
P365D) enables a periodic purge of older events — and the purge records its own
audit.purged event on every tier, so the log never shrinks silently. Retention is
deliberately configuration-only, with no UI: pruning policy belongs in reviewable deployment
configuration, not behind an admin session. The variables are in the
configuration reference.