Documentation

Install AccessFlow.

Last updated

Running AccessFlow

There is a guide for this. Run your first governed query walks a fresh install all the way to a query that is reviewed, approved and executed. This chapter is the reference behind it.

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:

shell — demo
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
Demo only. The root 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:

.env — production
# 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:

shell
# 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:

shell
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 — nightly pg_dump backups 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):

shell
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:

shell
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.

Internal testing only. Betas carry no upgrade-path guarantees toward the GA they precede. Treat their databases as disposable.

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:

docker-compose.override.yml
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:

shell
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:

  1. Create the organization and the first admin user (email + password).
  2. 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.
  3. 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.

Reconciliation order. Organization → admin user → notification channels → AI configs → review plans → datasources → SAML → OAuth2 → Langfuse → SMTP. Secret fields (passwords, API keys, webhook secrets) are read from Kubernetes Secrets, never from a ConfigMap. Full property tree: docs/09-deployment.md → Bootstrap configuration.
Multi-replica safety. Bootstrap is safe under 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.
Auditability. Bootstrap upserts are recorded in 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).