Operations
Health
GET /healthz and GET /api/health — same handler, both public.
$ curl -fsS http://localhost:4317/healthz
{"status":"ok","uptimeMs":1500,"schemaVersion":49,"serverProtocol":1,"version":"0.1.0","checks":{"store":"ok"}}
200 and ok, or 503 and degraded when the store is unreachable. That is the whole
check — it says nothing about workers, queue depth or disk space. A green health check
with every worker dead is a perfectly consistent state, so monitor worker liveness
separately.
The image carries a HEALTHCHECK against this endpoint, honouring PORT. The worker
has no HTTP server, so the check doesn't apply to it — compose disables it there, and a
bare docker run of a worker wants --no-healthcheck.
Logs
One JSON object per line:
{ "time": 1785132186586, "level": "info", "msg": "listening", "port": 4317 }
FW_LOG_LEVEL sets the level (debug · info · warn · error), adjustable at runtime
through the config file or admin API.
Structured logs are written to stderr, not stdout. Only two plain-text startup banner lines go to stdout. A log shipper configured to read stdout will collect almost nothing.
Secrets are redacted on the way out, which is why the log write goes through the server's
own writer rather than console.log.
Backups
What to back up: the whole data directory. state.db alone is not enough —
secret.key decrypts every stored credential, and losing it makes them unrecoverable.
SQLite
$ curl -fsS -H "Authorization: Bearer $TOKEN" \
http://localhost:4317/api/admin/backup -o flowwright-backup.db
A consistent snapshot, taken without stopping the server, reopenable as a database. Needs
settings:admin.
You still have to back up runs/ and secret.key separately — the endpoint snapshots
the database only.
PostgreSQL
The admin endpoint does not work, and says so:
$ curl -i -H "Authorization: Bearer $TOKEN" http://localhost:4317/api/admin/backup
HTTP/1.1 501 Not Implemented
{"error":"this endpoint snapshots the local SQLite store only; on PostgreSQL back up with pg_dump or pg_basebackup (and your artifact bucket separately)"}
Use pg_dump or pg_basebackup, plus your bucket's own replication or versioning for
artifacts.
Restoring
Stop everything, put the data directory back, start again. The schema version is in
/healthz; a snapshot from a newer server won't be readable by an older one.
Retention
Runs and artifacts accumulate. Three environment variables bound it:
| Variable | Bounds |
|---|---|
FW_RETENTION_RUNS | Runs kept per project |
FW_RETENTION_DAYS | Maximum run age |
FW_ARTIFACT_QUOTA_BYTES | Total artifact storage |
Or operational.retention in the config file, which is live-editable.
POST /api/admin/gc collects now instead of waiting.
This bounds runs and their artifacts only. The audit trail is exempt and never pruned; the notification inbox has its own fixed 90-day window.
Reloading configuration
$ docker compose kill -s HUP flowwright
Or POST /api/admin/reload. A bad file is non-fatal — the running config survives and
you get a 400. Check the response's restartRequired for what it read but couldn't
apply; see Configuration.
Upgrading
- Back up first.
- Pull the new image and recreate the server.
- Recreate workers.
Schema migrations run at server startup. Bring the server up before the workers so it migrates once rather than racing.
Pin a version. latest moves under you on the next pull.
If you're coming from an image that ran as root, the data volume needs a one-time chown — the compose page has the command.
Security checklist
- Keep
FW_AUTH=required.disabledmakes every caller an admin. - Set
FW_SECRET_KEYexplicitly, from a secret manager, so the key isn't sitting on the volume it protects. - Delete
initial-admin-passwordonce setup is done, and remember the password was in the log too. - Keep
FW_REGISTRATION_MODE=closedon anything reachable. - Terminate TLS in front and set
infra.externalBaseUrl. - Set
FW_METRICS_TOKENif you scrape/api/metrics. - Containers run as uid 1000. Add
read_only: truewith a writable/data, and drop capabilities, if your threat model wants it.