Skip to main content

Configuration

Three sources: environment variables, a YAML config file, and runtime edits through the admin API or UI. Environment alone is enough to run; the file exists for configuration-as-code, and it's the only way to declare projects and users.

Server environment​

VariableDefaultMeaning
PORT / FW_SERVER_PORT4317HTTP listen port
FW_DATA_DIR.flowwrightState root, resolved against the working directory
FW_WORKERS2In-process worker pool. 0 delegates to standalone workers
FW_LOG_LEVELinfodebug · info · warn · error
FW_AUTHrequiredrequired or disabled
FW_MFA_POLICYoptionaloptional · required_privileged · required_all
FW_REGISTRATION_MODEclosedopen lets any visitor create a member
FW_SECRET_KEYgeneratedCredential encryption key, 32 bytes as hex or base64
FW_WEBHOOK_SECRET—Shared secret for inbound provider webhooks
FW_METRICS_TOKEN—Bearer token accepted by /api/metrics
FW_DATABASE_URL—PostgreSQL connection string. Absent → SQLite
FW_ARTIFACTS_S3_BUCKET—S3 bucket for artifacts. Absent → local filesystem
FW_CONFIG_FILE<dataDir>/flowwright.server.yamlConfig file path
FW_RATE_LIMIT / FW_RATE_WINDOW_MS—Request rate limit and window
FW_RETENTION_RUNS / FW_RETENTION_DAYS—Garbage-collection thresholds
FW_ARTIFACT_QUOTA_BYTES—Artifact storage cap

Worker environment​

VariableDefaultMeaning
FW_DATA_DIR.flowwrightMust reach the server's state
FW_WORKERS / FW_CONCURRENCY2Stages this worker runs at once
FW_WORKER_LABELS—Comma-separated, e.g. linux,gpu
FW_WORKER_CAPABILITIES—Comma-separated, beyond auto-detected platform/arch
FW_WORKER_NAME / FW_WORKER_TOKEN—Worker identity and its token
FW_SERVER_URL—Control plane URL, for minting checkout tokens
FW_POLICY_FILE—Path to a JSON policy
FW_DOCKERautoauto · disabled · required
FW_DOCKER_USER / _PULL / _CPUS / _MEMORY / _NETWORK / _HOST / _CONTEXT—Container execution options
FW_KUBERNETESdisabledauto · required · disabled
FW_KUBERNETES_NAMESPACEdefaultNamespace for Kubernetes execution
Don't set both concurrency variables

FW_WORKERS and FW_CONCURRENCY both set worker concurrency. If both are present FW_WORKERS wins, with no warning.

FW_DATABASE_URL and FW_ARTIFACTS_S3_BUCKET must match the server's.

The config file​

<dataDir>/flowwright.server.yaml, or wherever FW_CONFIG_FILE points. YAML or JSON.

flowwright.server.yaml
infra:
externalBaseUrl: https://ci.example.com

security:
authMode: required
secretKey: { fromEnv: FW_SECRET_KEY }

operational:
logLevel: info
concurrency: 4
retention:
keepPerProject: 50
maxAgeDays: 90

resources:
projects:
- name: web
repo: github.com/acme/web
branch: main
users:
- name: ezequiel
role: admin

A declared project takes every field the UI exposes, not just the three above:

KeyDefault
namerequired — and the identity, see below
reporequired
branchthe repository's default
filepipeline.ts
setupauto-detected from a lockfile
schedulenone
archivedleft alone unless declared
priority0
maxConcurrencyunlimited
worker.labelsnone
worker.capabilitiesnone
worker.preferredLabelsnone

Note worker.labels and worker.capabilities here are requiredLabels and requiredCapabilities in the API — same fields, different names.

A project is identified by name, and an omitted key means "the default". Declaring a project without setup doesn't leave setup alone; it clears whatever the UI set, on every boot. Renaming a declared project in the UI makes the file create a second one. What each field does is on Projects.

Secrets are never literals. Every secret position takes { fromEnv: NAME } or { fromFile: /path }, and a plain string fails validation — so the file is safe to commit.

The worker has its own, flowwright.worker.yaml, with database, artifacts, concurrency, labels, capabilities, policy, credentialProviders, docker, kubernetes, network and identity.

Unknown keys are an error​

A misspelled key used to be stripped in silence — the file parsed, the server booted, and the setting did nothing. Now it fails, naming every typo at once:

config: unknown config key(s): secuirty, operational.retention.maxAgeDay

Two blocks that older files sometimes carry are rejected for the same reason, and both were already doing nothing: security.sourceControl (provider apps are registered through the admin UI, not the file) and resources.projects[].secrets (project credentials are created through the API).

security.credentialProviders is worker-only. Credentials are resolved by the worker, just-in-time before the declaring stage, so the block belongs in flowwright.worker.yaml. It is rejected in the server file rather than accepted and ignored.

What wins​

The file is authoritative for what it declares. Mechanically: values it declares are written into the database stamped file, everything reads the database, and a key the file stops declaring reverts to the environment default. A UI edit to a key the file never mentions persists.

Two exceptions where the environment beats the file, both deliberate: FW_DATABASE_URL and FW_ARTIFACTS_S3_BUCKET.

GET /api/config shows the effective value and its provenance — file, ui, or the default — which is the only reliable way to answer "why is this set to that".

Reloading​

$ kill -HUP <pid> # or: POST /api/admin/reload

Reload re-reads the file. A malformed file is non-fatal — the error is logged, the running configuration is untouched, and the API returns 400. A malformed file at boot is fatal.

What applies live: operational (log level, concurrency, rate limit, retention, UI defaults), security.authMode / mfaPolicy / registrationMode, infra.externalBaseUrl, scheduling, and resources.

What doesn't, and needs a restart:

  • infra.port
  • security.secretKey, webhookSecret, metricsToken
  • notifications.email
  • infra.outboundProxy
  • database, artifacts

Reload reports these in restartRequired rather than implying it applied them. It used to return an empty list, which is how you could change SMTP settings, get {"ok": true}, and have nothing happen.

The worker has no reload at all. Its config is boot-only; restart it.