Files
probo/contrib/helm/charts/probo/README.md
Cursor Agent 7191a28be2 Fix Helm chart self-hosting footguns
Address the highest-impact Kubernetes chart issues from #1245.
Align PG_ADDR with postgresql.host/port, wire
OAUTH2_SERVER_SIGNING_KEY, add early validation for required
base64 and PEM secrets, and isolate the main service/deployment
with component labels so Chrome pods are not selected by server
traffic.

Update chart values and documentation to clarify required secret
formats, managed PostgreSQL prerequisites, ACME account key
persistence, and current Azure Blob compatibility caveats for S3
proxy deployments.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>
2026-05-30 11:18:02 -07:00

11 KiB

Probo Helm Chart

This Helm chart deploys Probo - an open-source SOC-2 compliance platform - on Kubernetes.

Prerequisites

  • Kubernetes 1.23+
  • Helm 3.8+
  • External PostgreSQL database (AWS RDS, GCP Cloud SQL, Azure Database, etc.)
  • S3 or S3-compatible object storage (AWS S3, GCS, DigitalOcean Spaces, SeaweedFS, etc.)
  • OpenSSL installed (for generating secrets)

Azure Blob behind S3 compatibility proxies currently has known metadata compatibility issues. Prefer native S3-compatible backends for production.

Installing the Chart

From OCI Registry

helm install probo oci://artifact.probo.inc/probo/probo --version <chart-version> \
  --set probo.baseUrl="probo.example.com" \
  --set probo.encryptionKey="$ENCRYPTION_KEY" \
  --set probo.auth.cookieSecret="$COOKIE_SECRET" \
  --set probo.auth.passwordPepper="$PASSWORD_PEPPER" \
  --set probo.trustAuth.tokenSecret="$TRUST_TOKEN_SECRET" \
  --set-file probo.oauth2.signingKey="./oauth2_signing_key.pem" \
  --set postgresql.host="postgres.example.com" \
  --set postgresql.password="<db-password>" \
  --set s3.bucket="probo-production" \
  --set s3.accessKeyId="<aws-access-key-id>" \
  --set s3.secretAccessKey="<aws-secret-access-key>"

From Local Chart

Generate Required Secrets

# Generate required secrets
export ENCRYPTION_KEY=$(openssl rand -base64 32)
export COOKIE_SECRET=$(openssl rand -base64 32)
export PASSWORD_PEPPER=$(openssl rand -base64 32)
export TRUST_TOKEN_SECRET=$(openssl rand -base64 32)

# Generate OAuth2 signing key (PEM)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
  -out oauth2_signing_key.pem

# Generate private key and certificate valid for 10 years
TEMP_KEY=$(mktemp)
TEMP_CERT=$(mktemp)

openssl req -x509 -newkey rsa:2048 -keyout "$TEMP_KEY" -out "$TEMP_CERT" \
  -days 3650 -nodes -subj "/CN=probo-saml/O=Probo/C=US" 2>/dev/null

# Read generated files and export as environment variables
export SAML_PRIVATE_KEY=$(cat "$TEMP_KEY")
export SAML_CERTIFICATE=$(cat "$TEMP_CERT")

# Clean up temporary files
rm -f "$TEMP_KEY" "$TEMP_CERT"

echo "Save these secrets securely!"

Install

helm install probo . \
  --set probo.baseUrl="probo.example.com" \
  --set probo.encryptionKey="$ENCRYPTION_KEY" \
  --set probo.auth.cookieSecret="$COOKIE_SECRET" \
  --set probo.auth.passwordPepper="$PASSWORD_PEPPER" \
  --set probo.trustAuth.tokenSecret="$TRUST_TOKEN_SECRET" \
  --set-file probo.oauth2.signingKey="./oauth2_signing_key.pem" \
  --set probo.saml.privateKey="$SAML_PRIVATE_KEY" \
  --set probo.saml.certificate="$SAML_CERTIFICATE" \
  --set postgresql.host="postgres.example.com" \
  --set postgresql.password="<db-password>" \
  --set s3.bucket="probo-production" \
  --set s3.accessKeyId="<aws-access-key-id>" \
  --set s3.secretAccessKey="<aws-secret-access-key>"

Production Deployment

For production, create a values-production.yaml file:

# values-production.yaml
image:
  repository: artifact.probo.inc/probo/probo
  tag: "0.74.7"

replicaCount: 3

ingress:
  enabled: true
  className: "nginx"
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
  hosts:
    - host: probo.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: probo-tls
      hosts:
        - probo.example.com

probo:
  baseUrl: "probo.example.com"
  encryptionKey: "<secret>"
  cors:
    allowedOrigins:
      - "https://probo.example.com"
  auth:
    cookieDomain: "example.com"
    cookieSecret: "<secret>"
    passwordPepper: "<secret>"
  oauth2:
    signingKey: "<PEM private key>"
  trustAuth:
    cookieDomain: "example.com"
    tokenSecret: "<secret>"
  mailer:
    senderEmail: "noreply@example.com"
    smtp:
      addr: "smtp.sendgrid.net:587"
      user: "apikey"
      password: "<smtp-password>"
      tlsRequired: true

postgresql:
  host: "postgres.example.com"
  password: "<db-password>"

s3:
  region: "us-east-1"
  bucket: "probo-production"
  accessKeyId: "<aws-access-key-id>"
  secretAccessKey: "<aws-secret-access-key>"

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 10

Install with:

helm install probo . -f values-production.yaml

Configuration

Required Configuration

The following parameters must be configured:

Parameter Description
probo.encryptionKey Base64-encoded encryption key (32+ bytes)
probo.auth.cookieSecret Cookie signing secret (32+ bytes)
probo.auth.passwordPepper Password hashing pepper (32+ bytes)
probo.trustAuth.tokenSecret Trust token secret (32+ bytes)
probo.oauth2.signingKey PEM-encoded OAuth2 private signing key
postgresql.host PostgreSQL hostname
postgresql.password PostgreSQL password
s3.accessKeyId S3 access key ID
s3.secretAccessKey S3 secret access key

Secret Format Quick Reference

  • probo.encryptionKey, probo.auth.cookieSecret, probo.auth.passwordPepper, probo.trustAuth.tokenSecret: base64-encoded 32-byte secrets (for example: openssl rand -base64 32)
  • probo.oauth2.signingKey: PEM-encoded private key (--set-file strongly recommended)

Key Configuration Parameters

Parameter Description Default
image.repository Probo image repository artifact.probo.inc/probo/probo
image.tag Probo image tag Chart appVersion
replicaCount Number of Probo replicas 1
probo.baseUrl Public baseUrl probo.example.com
probo.oauth2.signingKey OAuth2 signing key (PEM private key) "" (required)
postgresql.host PostgreSQL host "" (required)
postgresql.port PostgreSQL port 5432
postgresql.database Database name probod
postgresql.username Database user probod
postgresql.caBundle PostgreSQL TLS CA certificate bundle (inline) ""
postgresql.caBundlePath PostgreSQL TLS CA certificate bundle (file path) ""
s3.bucket S3 bucket name probod
s3.region AWS region us-east-1
s3.endpoint S3 endpoint (for S3-compatible) ""
s3.usePathStyle Use path-style URLs for selected S3-compatible backends false
chrome.enabled Deploy Chrome true
chrome.external.addr External Chrome (if disabled) ""
ingress.enabled Enable ingress false

See values.yaml for all available options.

Components

The chart deploys the following:

  • Probo Application: Main Go binary serving GraphQL APIs and SPAs
  • Chrome Headless: For PDF generation (optional, can use external)
  • Ingress: For external access with TLS (optional)

External Dependencies (Required)

  • PostgreSQL: Managed database service
  • S3 Storage: Object storage for files and documents

Database Management

Migrations

Database migrations run automatically when Probo starts. No manual intervention is required.

For managed PostgreSQL providers (notably PostgreSQL >= 15), ensure the Probo role can own and modify the public schema before first startup:

ALTER SCHEMA public OWNER TO probod;
GRANT ALL ON SCHEMA public TO probod;

Probo migrations also require these extensions to be installable:

  • citext
  • pgcrypto
  • unaccent
  • pg_stat_statements

TLS/SSL Configuration

For secure PostgreSQL connections, you can provide a CA certificate bundle in two ways:

  1. Inline CA Bundle (postgresql.caBundle): Provide the certificate content directly in values.yaml

    postgresql:
      caBundle: |
        -----BEGIN CERTIFICATE-----
        MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
        ...
        -----END CERTIFICATE-----
    
  2. File Path (postgresql.caBundlePath): Mount the CA bundle as a ConfigMap/Secret and reference the path

    postgresql:
      caBundlePath: /etc/ssl/certs/ca-certificates.crt
    
    # Then mount your CA bundle using volumes/volumeMounts
    volumes:
      - name: ca-bundle
        configMap:
          name: postgres-ca-bundle
    volumeMounts:
      - name: ca-bundle
        mountPath: /etc/ssl/certs
        readOnly: true
    

Note: Using caBundlePath is recommended for large CA bundles (for example system CA bundles) as it avoids environment variable size limitations.

Backup

Use your PostgreSQL provider's backup solution (e.g., AWS RDS automated backups, GCP Cloud SQL backups).

Upgrading

helm upgrade probo . -f values-production.yaml

Uninstalling

helm uninstall probo

Note: This does not delete your external PostgreSQL database or S3 bucket.

Troubleshooting

View Logs

kubectl logs -f deployment/probo

Check Configuration

kubectl describe configmap probo
kubectl get secret probo -o yaml

Test Database Connection

Check the Probo logs for database connection errors. The application will fail to start if it cannot connect to PostgreSQL.

Test S3 Connection

Check the Probo logs for S3 connection errors when uploading files.

IP geolocation table is empty warning

If startup logs show:

IP geolocation table is empty; run geoloc-import to populate it

the instance is running correctly, but geolocation data is missing. Populate it by running the geoloc-import command in your operational workflow.

Examples

AWS Deployment

postgresql:
  host: "mydb.abc123.us-east-1.rds.amazonaws.com"
  password: "<rds-password>"
  # Optional: Add RDS CA bundle for TLS connections
  # caBundle: |
  #   -----BEGIN CERTIFICATE-----
  #   ...RDS CA certificate...
  #   -----END CERTIFICATE-----

s3:
  region: "us-east-1"
  bucket: "my-probo-bucket"
  accessKeyId: "<aws-key>"
  secretAccessKey: "<aws-secret>"
  # Leave endpoint empty for AWS S3

GCP Deployment

postgresql:
  host: "10.0.0.5"  # Cloud SQL private IP
  password: "<cloudsql-password>"

s3:
  region: "us-east1"
  bucket: "my-probo-bucket"
  endpoint: "https://storage.googleapis.com"
  accessKeyId: "<hmac-access-key>"
  secretAccessKey: "<hmac-secret>"

DigitalOcean Deployment

postgresql:
  host: "db-postgresql-nyc1-12345.ondigitalocean.com"
  password: "<db-password>"

s3:
  region: "nyc3"
  bucket: "my-probo-bucket"
  endpoint: "https://nyc3.digitaloceanspaces.com"
  accessKeyId: "<spaces-access-key>"
  secretAccessKey: "<spaces-secret>"

Using External Chrome Service

By default, Chrome is deployed in the cluster. To use an external Chrome service:

chrome:
  enabled: false
  external:
    addr: "chrome.browserless.io:3000"