Install AccessFlow.
Last updated
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.
How do I install AccessFlow?
Three ways. docker compose up against the repo root gives you a running demo stack in under a minute. helm install from the published chart is the production path on Kubernetes, generating its own secrets on first install. Building from source needs JDK 25 and Node.js 24, and is aimed at contributors.
System requirements & sizing
AccessFlow needs PostgreSQL 18 for its internal database and Redis 8 for caching and distributed locks — both ship inside the demo compose stack and the Helm chart. Rough sizing tiers:
| Tier | Resources | Notes |
|---|---|---|
| Evaluation | 2 vCPU · 4 GB RAM · 10 GB SSD | Single-node Docker Compose |
| Production | Backend ≥ 2× (2 vCPU · 2 GB) · Postgres (2 vCPU · 4 GB · 50 GB) · Redis (1 vCPU · 1 GB) | Scale backend replicas behind any L7 load balancer |
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 startup 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.
Release update hint. At most once a day the backend fetches the small
version.json file published next to the
changelog and, when a newer stable release exists, every
signed-in user sees the version under the sidebar logo turn into a link to that
release's changelog entry. The only thing that leaves your network is one
unauthenticated request for that static file — no install identifier, no usage data.
Set ACCESSFLOW_UPDATES_ENABLED=false on an air-gapped install to switch the
check off entirely; ACCESSFLOW_UPDATES_URL points a fork or an internal
mirror at its own manifest, and ACCESSFLOW_UPDATES_TTL (default
PT24H) sets how often it looks.
What else calls out on its own. Apart from that update hint, AccessFlow
starts outbound requests of its own accord in two more places, and both are yours to
switch off. Database drivers and engine plugins download on demand — set
ACCESSFLOW_DRIVERS_OFFLINE=true and pre-seed the driver cache. And the
in-app help assistant, which answers questions about how to use AccessFlow from a copy of
this documentation shipped inside the release, can optionally pick up corrected
documentation published between releases; that refresh is off unless you switch it on,
and ACCESSFLOW_HELP_CORPUS_OFFLINE=true keeps it off for good. Set all three
and AccessFlow itself never reaches the internet. The integrations you configure
are separate and keep calling out as configured — the AI provider (including the one the
help assistant answers with, unless you run a local model), your identity provider, an
external secrets manager, notification channels. The help assistant's documentation is
always read from inside the release you are running, never fetched per question.
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). While no active admin user
exists, the app routes to /setup and walks you through:
- Create the organization and the first admin user (email + password).
- Choose what you plan to govern. Database access governance is always on; two switches add outbound API calls and CI/CD deployments. Both are off by default and you can change the answer later.
- Optional: configure system SMTP so invitation emails work.
The domain choice shapes what the app shows you, not what you are allowed to do:
the onboarding checklist, the sidebar sections, the review-queue tabs and the dashboard
widgets for a domain you left off simply are not there. Nobody loses a permission, no page
stops working, and a link someone already has still opens. Turn a domain on later at
/admin/governance-domains and everything appears.
After the wizard finishes you land on the app as the admin, with a checklist covering the remaining setup — a review plan, an AI provider, your first datasource, plus a first connector or pipeline for each domain you picked. Every step links to the page that owns it; 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 scheduled-job
locking 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).