Self-hosting

Built to run inside your environment

One container image, PostgreSQL and a Helm chart. The same chart runs on a laptop, a home-lab k3s cluster or production EKS, including networks with no internet access.

How Workflow runs People on your network reach the web pods; Git hosts and SCIM reach only the hooks pods from the internet. Web, hooks and worker pods run the same image and keep all their state in PostgreSQL, run by CloudNativePG, optionally behind PgBouncer. The worker sends email, chat messages and webhooks, and syncs your directory. YOUR KUBERNETES CLUSTER Your team Browser, CLI, assistants GitHub, GitLab, SCIM From the internet web UI, API, live updates hooks Webhooks and SCIM only worker Jobs, sync, email, chat PostgreSQL CloudNativePG, optional PgBouncer Issues, search, job queue, audit log, attachments Out to SMTP, chat, your IdP internal public One image, three modes: --mode=web | hooks | worker Each scales on its own: HPA for web and hooks, KEDA on queue depth for workers

Only the hooks endpoint needs to face the internet; in an air-gapped install, nothing does.

Install

Install with Helm or Argo CD

The chart brings a CloudNativePG database, Ingress or Gateway API routes, HPA and KEDA autoscaling, PodDisruptionBudgets, topology spread and NetworkPolicies. Overlays for k3s and EKS mean only values change between clusters.

Every setting is an environment variable. Secrets come from a Secret you manage, a SOPS-encrypted file, or External Secrets.

  • Backups and point-in-time restore through CloudNativePG
  • Attachments in PostgreSQL, or in S3 and S3-compatible storage
Get started →
Install
helm upgrade --install wfm deploy/helm/wfm -n wfm --create-namespace \
  -f deploy/helm/wfm/values-k3s.yaml -f my-values.yaml

# my-values.yaml
config:
  WFM_PUBLIC_URL: https://workflow.home.example.net
  WFM_HOOKS_PUBLIC_URL: https://hooks.example.net
  WFM_OIDC_PROVIDERS: keycloak
  WFM_OIDC_KEYCLOAK_ISSUER: https://sso.example.net/realms/acme
database:
  cnpg:
    pooler: { enabled: true }   # PgBouncer in front of the primary

Upgrades

Upgrades without downtime

Migrations run as an Argo CD PreSync job, or a Helm hook, before new pods start while the old version keeps serving. Every schema change is written expand-then-contract, so old and new versions can run side by side during a rollout.

The database is protected from accidental deletion: the chart keeps the cluster when the release is removed, and Argo CD won't prune it.

What a rollout does
1. PreSync job    wfm migrate
                  the old pods keep serving
2. Rollout        web, hooks and worker
                  readiness gates the traffic
3. Next release   drops what the old version needed

Supply chain

Air-gapped and signed

Each release ships offline bundles for amd64 and arm64 with the images and the chart. Images and charts are signed with Cosign, with an SBOM attestation and build provenance, so you can verify them before they run or enforce it with an admission policy.

Images are distroless and run as a non-root user.

Read the deployment guide →
Verify before you deploy
IMG=ghcr.io/workflowproj/workflow@sha256:...
cosign verify "$IMG" \
  --certificate-identity-regexp '^https://github.com/workflowproj/workflow/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
cosign verify-attestation "$IMG" --type spdxjson \
  --certificate-identity-regexp '^https://github.com/workflowproj/workflow/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

Operating it

Ready for production

What you need to run it with confidence, already in the chart.

Metrics and alerts

Prometheus metrics with a ServiceMonitor, a Grafana dashboard and alert rules for errors, webhook ingestion, readiness and database replication.

Traces and logs

OpenTelemetry traces over OTLP and JSON logs. Liveness never touches the database; readiness does, so an outage drains traffic instead of restarting pods.

Network policies

NetworkPolicies on by default: ingress denied unless allowed, the hooks pods reachable only from the public ingress, and egress limited to what each mode needs.

Limits and guards

Request limits per token, session and address, sign-in throttling and lockout, and a guard against outbound requests to internal addresses.

Secrets at rest

Git host keys, webhook secrets, chat webhook addresses and import tokens are stored encrypted with your secret key.

Measured at scale

Searches, boards and reports are measured against 100,000 issues and 360,000 history rows.

Run it on your own infrastructure

Free and open source under Apache 2.0, with one edition that has everything. The docs cover installation, configuration and every feature.