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_SECRETShared secret for inbound provider webhooks
FW_METRICS_TOKENBearer token accepted by /api/metrics
FW_DATABASE_URLPostgreSQL connection string. Absent → SQLite
FW_ARTIFACTS_S3_BUCKETS3 bucket for artifacts. Absent → local filesystem
FW_CONFIG_FILE<dataDir>/flowwright.server.yamlConfig file path
FW_RATE_LIMIT / FW_RATE_WINDOW_MSRequest rate limit and window
FW_RETENTION_RUNS / FW_RETENTION_DAYSGarbage-collection thresholds
FW_ARTIFACT_QUOTA_BYTESArtifact 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_LABELSComma-separated, e.g. linux,gpu
FW_WORKER_CAPABILITIESComma-separated, beyond auto-detected platform/arch
FW_WORKER_NAME / FW_WORKER_TOKENWorker identity and its token
FW_SERVER_URLControl plane URL, for minting checkout tokens
FW_POLICY_FILEPath to a JSON policy
FW_DOCKERautoauto · disabled · required
FW_DOCKER_USER / _PULL / _CPUS / _MEMORY / _NETWORK / _HOST / _CONTEXTContainer 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.