Skip to main content

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.

Logs go to stderr

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:

VariableBounds
FW_RETENTION_RUNSRuns kept per project
FW_RETENTION_DAYSMaximum run age
FW_ARTIFACT_QUOTA_BYTESTotal 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

  1. Back up first.
  2. Pull the new image and recreate the server.
  3. 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. disabled makes every caller an admin.
  • Set FW_SECRET_KEY explicitly, from a secret manager, so the key isn't sitting on the volume it protects.
  • Delete initial-admin-password once setup is done, and remember the password was in the log too.
  • Keep FW_REGISTRATION_MODE=closed on anything reachable.
  • Terminate TLS in front and set infra.externalBaseUrl.
  • Set FW_METRICS_TOKEN if you scrape /api/metrics.
  • Containers run as uid 1000. Add read_only: true with a writable /data, and drop capabilities, if your threat model wants it.