Install AccessFlow.
Running AccessFlow
Pick one of three modes. Docker Compose is the fastest path to a running instance. Helm is the production-recommended path on Kubernetes. The manual / from-source path is for contributors and for environments where containers aren't available.
Docker Compose
The repo root ships a zero-config demo stack — it pulls the published images from GHCR and starts Postgres + Redis + backend + frontend with insecure demo keys baked in so a fresh clone runs with one command:
git clone https://github.com/bablsoft/accessflow.git cd accessflow docker compose up -d # open http://localhost:5173 — the in-app setup wizard creates the first admin
docker-compose.yml embeds insecure
JWT_PRIVATE_KEY and ENCRYPTION_KEY defaults inline so it
works on a fresh clone. Do not deploy this to anything but a sandbox.
For real environments, use the production-style compose below or the Helm chart.
For a production-style compose, generate real keys and supply them via
.env:
# 32-byte hex — AES-256-GCM for datasource credential encryption ENCRYPTION_KEY=$(openssl rand -hex 32) # RSA-2048 PEM — JWT RS256 signing key JWT_PRIVATE_KEY="$(openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 2>/dev/null)" DB_PASSWORD=change-me # Password for the dedicated audit-writer role (issue #67). Provisioned by # deploy/postgres-init/01-audit-role.sql — see "audit_log role separation". AUDIT_DB_PASSWORD=change-me-audit CORS_ALLOWED_ORIGIN=https://accessflow.company.com ACCESSFLOW_PUBLIC_BASE_URL=https://accessflow.company.com
See docs/09-deployment.md
→ Docker Compose for the full production-style compose (including the optional
ollama profile for self-hosted AI).
Structured logs for ELK / OpenSearch. Set
ACCESSFLOW_LOGGING_STRUCTURED_FORMAT=logstash (or ecs /
gelf) on the backend container and every log line becomes a single JSON
object — `traceId` and `spanId` from the Micrometer tracing bridge are top-level
fields, so correlation works out of the box. The Spring Boot ASCII banner is hidden by
default; set SPRING_MAIN_BANNER_MODE=console to restore it.
Tracing & metrics. Set
OTEL_EXPORTER_OTLP_ENDPOINT to your collector's full OTLP/HTTP traces URL
(e.g. http://tempo:4318/v1/traces) and AccessFlow exports the proxy-pipeline
spans — parse → AI analyze → pool acquire → execute — to Tempo / Jaeger / Honeycomb
(sampling via ACCESSFLOW_TRACING_SAMPLING_PROBABILITY; export is off until an
endpoint is set). Prometheus metrics are exposed at /actuator/prometheus
(unauthenticated for in-cluster scraping — keep /actuator off the public
ingress). The Helm chart ships two pre-built Grafana dashboards behind
dashboards.enabled=true covering query volume, approval SLAs, AI usage/cost,
rejection rates, and connection-pool stats — see
examples/values-observability.yaml.
External secrets managers. Datasource credentials can live in
HashiCorp Vault, AWS Secrets Manager, or
Azure Key Vault instead of the built-in AES layer: enable a provider
(ACCESSFLOW_SECRETS_VAULT_ENABLED=true +
ACCESSFLOW_SECRETS_VAULT_URI/_TOKEN (or AppRole /
Kubernetes auth), ACCESSFLOW_SECRETS_AWS_ENABLED=true (region and
credentials from the SDK default chain — env vars, IRSA, instance profile — or
explicit _REGION/_ACCESS_KEY_ID/_SECRET_ACCESS_KEY),
or ACCESSFLOW_SECRETS_AZURE_ENABLED=true +
ACCESSFLOW_SECRETS_AZURE_VAULT_URL (workload/managed identity or an
explicit client-secret credential)) and enter a secret reference in a
datasource's password field instead of the raw value:
vault:<mount>/<path>#<field>,
aws:<name-or-arn>[#jsonField], or
azure:<secret-name>. AccessFlow resolves the reference through the
store at connection time (never caching the value), audits every resolve, and keeps
local AES-256-GCM encryption as the default for plain passwords. Full variable
reference:
docs/09-deployment.md
→ Secrets Manager.
Kubernetes & Helm
The Helm chart ships at charts/accessflow/ and is published to
https://bablsoft.github.io/accessflow. With defaults, no Secrets need
to be pre-created — the chart auto-generates the encryption key, the JWT private
key, and the PostgreSQL password on first install, and preserves them across
helm upgrade:
# 1. add the chart repo helm repo add accessflow https://bablsoft.github.io/accessflow helm repo update # 2. install — chart auto-generates secrets on first run helm install accessflow accessflow/accessflow \ --namespace accessflow --create-namespace \ -f values.yaml
Prefer to manage the secrets yourself (sealed-secrets, External Secrets, Vault, …)?
Pre-create them and point config.encryptionKey.existingSecret,
config.jwtPrivateKey.existingSecret, and
postgresql.auth.existingSecret at your own resources:
kubectl -n accessflow create secret generic accessflow-encryption-key \ --from-literal=value="$(openssl rand -hex 32)" kubectl -n accessflow create secret generic accessflow-jwt-key \ --from-file=value=<(openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048) # Postgres Secret needs BOTH keys: `password` (AccessFlow user) + `postgres-password` (admin) kubectl -n accessflow create secret generic accessflow-pg-secret \ --from-literal=password="$(openssl rand -base64 24)" \ --from-literal=postgres-password="$(openssl rand -base64 24)"
The chart bundles Bitnami subcharts for Postgres and Redis (toggle off with
postgresql.enabled=false / redis.enabled=false to point at
external instances). TLS on the Ingress is off by default; set
ingress.tls.enabled=true with a secretName and (optionally)
a cert-manager annotation to terminate HTTPS. Full values reference:
docs/09-deployment.md
→ Kubernetes & Helm and
charts/accessflow/README.md.
Ready-made starting points live under
charts/accessflow/examples/ —
each file is a minimal override on top of the chart's values.yaml.
They split into deployment shapes (cluster-level: replicas,
ingress, secrets model) and bootstrap slices (declarative
admin config). The intended pattern is one of each, plus your own
site-specific overrides.
Deployment shapes:
-
values-minimal.yaml— single-replica demo over plain HTTP. -
values-production.yaml— HA backend (HPA + PDB + pod anti-affinity), cert-manager-issued TLS, persistent JDBC driver cache. -
values-external-services.yaml— managed Postgres + Redis (RDS / ElastiCache / …) with every secret managed outside the chart. -
values-airgapped.yaml— air-gapped: internal registry mirror, offline JDBC drivers, manual TLS Secret. -
values-backup.yaml— nightlypg_dumpbackups to a PVC with retention pruning, optional rclone upload (S3/GCS/…), and the one-shot restore Job.
Backup, restore & disaster recovery. Set
backup.enabled=true to run a nightly pg_dump CronJob of the
AccessFlow database onto a dedicated backups volume (kept across
helm uninstall), pruned to backup.retention.keepLast dumps,
with an optional rclone step shipping the directory to S3 / GCS / Azure / SFTP. To
restore: scale the backend to zero, run
helm upgrade --reuse-values --set restore.enabled=true --set restore.dumpFile=<name>.dump
(the hook Job re-provisions the audit-writer role and replays the dump with ownership
and grants intact), then boot the backend once with
ACCESSFLOW_AUDIT_VERIFY_CHAIN_ON_STARTUP=true — every organization's
tamper-evident audit chain is re-verified and the outcome logged per organization.
Verification needs the same AUDIT_HMAC_KEY / ENCRYPTION_KEY
as when the rows were written, so back those Secrets up alongside the dumps. Full
runbook (including external-database failover):
docs/09-deployment.md → Disaster Recovery.
Bootstrap slices (each declares organization + first admin user and is meant to layer on a deployment shape):
-
values-bootstrap-minimal.yaml— just the organization + first admin user, nothing else. -
values-bootstrap-oauth2-sso.yaml— adds OAuth2 providers (Google, Microsoft Entra ID, GitHub). -
values-bootstrap-saml-sso.yaml— adds SAML 2.0 SP wired to a corporate IdP (Okta, Azure AD, JumpCloud, Auth0, ADFS). -
values-bootstrap-datasources.yaml— AI provider + tiered review plans + multi-dialect datasources (Postgres, MySQL, MSSQL). -
values-bootstrap-notifications.yaml— system SMTP relay + Slack / email / webhook channels + a fan-out review plan. -
values-bootstrap.yaml— kitchen-sink reference covering everybootstrap.*field at once.
helm install accessflow accessflow/accessflow \
--namespace accessflow --create-namespace \
-f charts/accessflow/examples/values-production.yaml \
-f charts/accessflow/examples/values-bootstrap-oauth2-sso.yaml \
-f my-site-overrides.yaml
Manual / from source
For contributors and air-gapped builds. Requires JDK 25 and Node.js 24 on the host:
git clone https://github.com/bablsoft/accessflow.git cd accessflow # 1. infrastructure — Postgres 18 + Redis 8 + Mailcrab (dev-only compose) docker compose -f backend/docker-compose-dev.yml up -d # 2. backend cd backend ./mvnw spring-boot:run # 3. frontend (in another shell) cd frontend npm install npm run dev # open http://localhost:5173
For full coding standards, test commands, and the dev loop, see docs/11-development.md.
Beta / pre-release channel
Pre-release builds are cut for internal testing ahead of a stable release. They are
published with a -beta.N / -rc.N version tag and a moving
:beta Docker tag — never :latest — so a beta
never disturbs production: a plain docker compose up or
helm install stays on the last stable release until you opt in.
Docker Compose — drop a docker-compose.override.yml next
to the demo file (Compose merges it automatically), then
docker compose pull && docker compose up -d:
services: backend: image: ghcr.io/bablsoft/accessflow-backend:beta # or :1.2.0-beta.1 frontend: image: ghcr.io/bablsoft/accessflow-frontend:beta # or :1.2.0-beta.1
Helm — pre-release chart versions are hidden from default resolution,
so opt in with --devel and pin the exact version:
helm repo update helm install accessflow accessflow/accessflow \ --version 1.2.0-beta.1 --devel \ --namespace accessflow --create-namespace
See docs/09-deployment.md → Installing a pre-release / beta build for the full consumer guide.
First-time setup
There are two ways to bring up a brand-new AccessFlow deployment.
Option A — Browser setup wizard
The default. Open the frontend (http://localhost:5173 for the demo
stack, or whatever URL serves the SPA in your environment). When no organization
exists yet, the app routes to /setup and walks you through:
- Create the organization.
- Create the first admin user (email + password).
- Optional: configure system SMTP so invitation emails work.
- Optional: add a first datasource and a default review plan.
After the wizard finishes, log in as the admin and finish configuring the system from the admin pages — see Configuration below.
Option B — Bootstrap via env vars (GitOps)
Set ACCESSFLOW_BOOTSTRAP_ENABLED=true and supply
ACCESSFLOW_BOOTSTRAP_* properties for the organization, admin user,
review plans, AI configs, datasources, SAML, OAuth2, Langfuse, notification channels, and
system SMTP. On every startup the bootstrap module reconciles the declared
configuration into the database — declared rows are upserted, omitted rows are left
alone.
replicaCount.backend > 1: every pod races for a Redis-backed
bootstrapReconcile lock (the same Redis instance that powers ShedLock
and JWT refresh tokens), so exactly one replica performs the upserts per startup
wave. The other replicas log an INFO line and keep serving traffic. No additional
env vars to configure.
audit_log with actor_id = NULL and
metadata.source = "BOOTSTRAP", so operators can answer "who changed
this — me, or a helm upgrade?" from a single source of truth. Restarting with
unchanged env vars writes zero new rows (the reconciler caches a SHA-256
fingerprint of each declared spec in bootstrap_state and short-circuits
on a match).