Skip to main content

Quickstart

Assumes you already have a cluster, a PostgreSQL instance, an object storage bucket, and an ingress controller.

1. Create the namespace

kubectl create namespace presponsieve

2. Prepare the database

The migration Job creates the schema, but not the database or the role.

CREATE ROLE presponsieve WITH LOGIN PASSWORD '...';
CREATE DATABASE presponsieve OWNER presponsieve;

3. Create the application secret

Every sensitive value lives in one secret.

kubectl create secret generic presponsieve-secrets -n presponsieve \
--from-literal=APP_KEK="$(openssl rand -base64 32)" \
--from-literal=SESSION_SECRET="$(openssl rand -base64 32)" \
--from-literal=INDEX_PEPPER="$(openssl rand -base64 32)" \
--from-literal=AUTH_TOKEN_PEPPER="$(openssl rand -base64 32)" \
--from-literal=LICENSE_KEY="$LICENSE_KEY"
warning

Capture APP_KEK as you generate it and store it outside the cluster. The command above prints nothing, and there is no way to recover the key from the Secret if the namespace is lost. Every encrypted row becomes unreadable without it.

For production, use External Secrets or Sealed Secrets rather than kubectl create secret. See Secrets.

4. Write a values file

my-values.yaml
# Without this the chart names resources <release>-<chart>, which renders as
# "presponsieve-presponsieve". Every example sets it.
fullnameOverride: presponsieve

image:
tag: "0.1.0"

app:
existingSecret: presponsieve-secrets
config:
KMS_BACKEND: "k8s"
LICENSE_ENFORCEMENT: "enforce"
S3_PREFIX: "reports"

postgresql:
enabled: false
externalDatabase:
url: postgresql+psycopg2://presponsieve:PASS@db.internal:5432/presponsieve

minio:
enabled: false
externalObjectStore:
endpointUrl: ""
bucket: acme-presponsieve-artifacts
region: us-east-1

keycloak:
enabled: false
oidc:
issuerUrl: https://login.acme.com/oauth2/default
clientId: presponsieve

ingress:
enabled: true
className: nginx
host: app.internal.acme.com
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
tls:
enabled: true
secretName: presponsieve-tls

autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 5

migration:
enabled: true

Putting the DSN in externalDatabase.url writes the database password into your values file. Move it into the application secret as DATABASE_URL once you are past evaluation.

5. Render, then install

helm template presponsieve oci://ghcr.io/m-a-m-a-ai/charts/presponsievelite \
-f my-values.yaml | less

helm upgrade --install presponsieve \
oci://ghcr.io/m-a-m-a-ai/charts/presponsievelite \
-n presponsieve -f my-values.yaml --set image.tag=0.1.0

The pre-install hook runs migrations before app pods start, so an unreachable database fails the install cleanly.

6. Verify

kubectl get pods,job -n presponsieve
kubectl port-forward -n presponsieve svc/presponsieve 8080:80
curl -s localhost:8080/healthz # {"status":"ok"}

Then sign in and run one analysis. Confirm the report, radar image, and PDF all come back — that single flow exercises the database, the bucket, and signed-URL retrieval at once.

What you did not enable

Narrative rendering. Without OPENAI_API_KEY the engine returns structured, deterministic output rather than prose. That is a supported configuration, not a broken one. See Rendering.