feat: add helm charts

Signed-off-by: Thomas Stocker <thomas.stocker.pro@gmail.com>
This commit is contained in:
Thomas Stocker
2025-11-04 10:58:53 +01:00
committed by Bryan Frimin
parent 6d1760ce9b
commit 4a96bd796a
23 changed files with 2484 additions and 0 deletions

22
contrib/helm/.gitignore vendored Normal file
View File

@@ -0,0 +1,22 @@
# General files for the project
pkg/*
*.pyc
bin/*
.project
/.bin
/_test/secrets/*.json
# OSX leaves these everywhere on SMB shares
._*
# OSX trash
.DS_Store
# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA
.idea/
*.iml
# Vscode files
.vscode
charts/probo/charts

474
contrib/helm/README.md Normal file
View File

@@ -0,0 +1,474 @@
# Probo Kubernetes Deployment
This directory contains the Helm chart for deploying Probo on Kubernetes with external managed services.
## Quick Links
- [Helm Chart Documentation](charts/probo/README.md)
- [Values Reference](charts/probo/values.yaml)
## Prerequisites
Before deploying Probo, ensure you have:
1. **Kubernetes Cluster** - Version 1.23+
2. **Helm** - Version 3.8+
3. **PostgreSQL Database** - Managed service (AWS RDS, GCP Cloud SQL, Azure Database, etc.)
4. **S3 Storage** - AWS S3 or S3-compatible storage (GCS, DigitalOcean Spaces, MinIO, etc.)
## Quick Start
### 1. Generate Secrets
```bash
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)
echo "Save these secrets securely!"
```
### 2. Install
## Install
#### Using Official Chart Repository
> Have Helm 3 [installed](https://helm.sh/docs/intro/install).
```sh
helm repo add probo https://getprobo.github.io/probo-helm-charts/
helm install probo probo/probo -n probo --create-namespace --values values.yaml
```
To update versions:
```
helm repo update probo
helm upgrade probo probo/probo -n probo --values values.yaml
```
#### Using Local Chart
##### Generate Secrets
```bash
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)
# Save these somewhere secure!
echo "ENCRYPTION_KEY=$ENCRYPTION_KEY"
echo "COOKIE_SECRET=$COOKIE_SECRET"
echo "PASSWORD_PEPPER=$PASSWORD_PEPPER"
echo "TRUST_TOKEN_SECRET=$TRUST_TOKEN_SECRET"
```
##### Install using Chart and set values
```bash
helm install my-probo ./charts/probo \
--set probo.hostname="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 postgresql.enabled=true \
--set postgres.auth.postgresUser="probod" \
--set postgres.auth.postgresPassword="your-db-password" \
--set postgres.auth.database="probod" \
--set minio.enabled=true \
--set s3.bucket="your-bucket-name" \
--set s3.accessKeyId="your-access-key" \
--set s3.secretAccessKey="your-secret-key"
```
##### Install using Chart and values file
```bash
helm install my-probo ./charts/probo \
--set probo.encryptionKey="$ENCRYPTION_KEY" \
--set probo.auth.cookieSecret="$COOKIE_SECRET" \
--set probo.auth.passwordPepper="$PASSWORD_PEPPER" \
--set probo.trustAuth.tokenSecret="$TRUST_TOKEN_SECRET" \
-f ./charts/probo/values.yaml
```
### 3. Access
```bash
kubectl port-forward svc/probo 8080:8080
# Visit http://localhost:8080
```
## Production Deployment
For production deployments, we recommend:
1. **Copy the production template:**
```bash
cp charts/probo/values.yaml charts/probo/values-k8s-production.yaml
```
2. **Edit the configuration:**
- Set your domain name
- Configure external PostgreSQL connection
- Configure S3 storage credentials
- Add SMTP settings for email
- Enable ingress with TLS
- Configure autoscaling
3. **Install:**
```bash
helm install probo ./charts/probo -f ./charts/probo/values-k8s-production.yaml
```
## Architecture
### What Gets Deployed
- **Probo Application** - Main Go binary serving GraphQL APIs and React frontends
- **Chrome Headless** - For PDF generation (optional, can use external service)
- **LoadBalancer Service** - For external access via TCP on ports 80, 443, and 8080
- **Ingress** - Alternative to LoadBalancer for HTTP routing (optional)
### External Dependencies (Required)
- **PostgreSQL** - Managed database for compliance data
- **S3 Storage** - Object storage for files and documents
The chart is designed to work with managed cloud services, ensuring reliability and scalability.
### Deployment Mode: HAProxy Ingress (Default)
The default configuration uses HAProxy Ingress controller which provides both Layer 4 (TCP passthrough) and Layer 7 (HTTP routing) capabilities.
**Architecture:**
```
Client
↓
HAProxy Ingress LoadBalancer
├─ Port 80 (TCP Layer 4 passthrough) → probo:80
├─ Port 443 (TCP Layer 4 passthrough) → probo:443
└─ HTTP probo.example.com (Layer 7 routing) → probo:8080
```
**How it works:**
1. **HAProxy Ingress Controller** runs in the release namespace with a LoadBalancer service
2. **TCP ConfigMap** defines Layer 4 TCP passthrough rules:
- Port 80 → Probo service port 80
- Port 443 → Probo service port 443
3. **HTTP Ingress** defines Layer 7 HTTP routing:
- Host `probo.example.com` → Probo service port 8080
**Benefits:**
- Single LoadBalancer for both TCP and HTTP traffic
- TCP passthrough for ports 80/443 (Probo handles TLS directly)
- HTTP routing for backoffice on port 8080
- Supports ACME/Let's Encrypt integration
**Port Configuration:**
- **Port 80** - TCP passthrough to Probo:80 (HTTP service, ACME challenges)
- **Port 443** - TCP passthrough to Probo:443 (HTTPS service with TLS)
- **Port 8080** - HTTP routing to Probo:8080 (Backoffice, via host-based routing)
## Configuration
### Required Configuration
All deployments require:
- `probo.encryptionKey` - For data encryption at rest
- `probo.auth.cookieSecret` - For session management
- `probo.auth.passwordPepper` - For password hashing
- `probo.trustAuth.tokenSecret` - For trust center tokens
- `postgresql.host` - PostgreSQL server hostname
- `postgresql.password` - Database password
- `s3.accessKeyId` - S3 access credentials
- `s3.secretAccessKey` - S3 secret key
See [values.yaml](charts/probo/values.yaml) for all available options.
## Cloud Provider Examples
### AWS
- PostgreSQL: Amazon RDS for PostgreSQL
- Storage: Amazon S3
- Kubernetes: Amazon EKS
#### Exemple
```bash
# Prerequisites:
# - Amazon RDS PostgreSQL instance
# - S3 bucket created
helm install my-probo ././charts/probo \
--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 postgresql.host="mydb.abc123.us-east-1.rds.amazonaws.com" \
--set postgresql.password="<rds-password>" \
--set s3.region="us-east-1" \
--set s3.bucket="my-probo-bucket" \
--set s3.accessKeyId="<aws-access-key>" \
--set s3.secretAccessKey="<aws-secret-key>"
```
### GCP
- PostgreSQL: Cloud SQL for PostgreSQL
- Storage: Cloud Storage with S3 compatibility
- Kubernetes: Google Kubernetes Engine (GKE)
#### Exemple
```bash
# Prerequisites:
# - Cloud SQL PostgreSQL instance
# - Cloud Storage bucket with HMAC keys
helm install my-probo ././charts/probo \
--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 postgresql.host="10.0.0.5" \
--set postgresql.password="<cloudsql-password>" \
--set s3.endpoint="https://storage.googleapis.com" \
--set s3.bucket="my-probo-bucket" \
--set s3.accessKeyId="<hmac-access-key>" \
--set s3.secretAccessKey="<hmac-secret>"
```
### Azure
- PostgreSQL: Azure Database for PostgreSQL
- Storage: Azure Blob Storage (with S3 compatibility)
- Kubernetes: Azure Kubernetes Service (AKS)
#### Exemple
```bash
# Prerequisites:
# - Azure Database for PostgreSQL instance
# - Azure Blob Storage container with S3 compatibility
helm install my-probo ././charts/probo \
--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 postgresql.host="mydb.postgres.database.azure.com" \
--set postgresql.password="<azure-db-password>" \
--set s3.endpoint="https://<your-storage-account>.blob.core.windows.net" \
--set s3.bucket="my-probo-bucket" \
--set s3.accessKeyId="<azure-access-key>" \
--set s3.secretAccessKey="<azure-secret-key>"
```
### DigitalOcean
- PostgreSQL: Managed PostgreSQL Database
- Storage: DigitalOcean Spaces
- Kubernetes: DigitalOcean Kubernetes (DOKS)
#### Exemple
```bash
# Prerequisites:
# - Managed PostgreSQL Database
# - Spaces bucket
helm install my-probo ././charts/probo \
--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 postgresql.host="db-postgresql-nyc1-12345.ondigitalocean.com" \
--set postgresql.password="<db-password>" \
--set s3.region="nyc3" \
--set s3.endpoint="https://nyc3.digitaloceanspaces.com" \
--set s3.bucket="my-probo-bucket" \
--set s3.accessKeyId="<spaces-key>" \
--set s3.secretAccessKey="<spaces-secret>"
```
### Using External Secrets Operator
Example with AWS Secrets Manager:
```yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: probo-secrets
spec:
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: probo
data:
- secretKey: encryption-key
remoteRef:
key: probo/encryption-key
- secretKey: db-password
remoteRef:
key: probo/db-password
```
## Full Values
| Key | Type | Default | Description |
|-----------------------------------------------| ------- |----------------------------------------------------|-----------------------------------------------------------------------------------------------------|
| replicaCount | int | `1` | Number of Probo application replicas |
| image.repository | string | `"ghcr.io/getprobo/probo"` | Container image repository |
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
| image.tag | string | `"latest"` | Overrides the image tag whose default is the chart appVersion |
| imagePullSecrets | list | `[]` | Image pull secrets for private registries |
| nameOverride | string | `""` | Override the name of the chart |
| fullnameOverride | string | `""` | Override the fullname of the chart |
| serviceAccount.create | bool | `true` | Specifies whether a service account should be created |
| serviceAccount.automount | bool | `true` | Automatically mount a ServiceAccount's API credentials |
| serviceAccount.annotations | object | `{}` | Annotations to add to the service account |
| serviceAccount.name | string | `""` | The name of the service account to use |
| podAnnotations | object | `{}` | Annotations to add to pods |
| podLabels | object | `{}` | Labels to add to pods |
| podSecurityContext.runAsUser | int | `0` | User ID to run the container as (0 = root, change to 1000 for production) |
| podSecurityContext.runAsGroup | int | `0` | Group ID to run the container as (0 = root, change to 1000 for production) |
| securityContext.runAsUser | int | `0` | User ID for the security context |
| securityContext.runAsGroup | int | `0` | Group ID for the security context |
| securityContext.privileged | bool | `true` | Enable privileged mode for the container (set to false for production) |
| securityContext.capabilities.drop | list | `["ALL"]` | Linux capabilities to drop from the container |
| securityContext.readOnlyRootFilesystem | bool | `false` | Mount root filesystem as read-only (/data directory requires write access) |
| service.type | string | `"ClusterIP"` | Kubernetes service type |
| service.port | int | `8080` | Service port |
| service.httpPort | int | `80` | HTTP port for TCP passthrough |
| service.httpsPort | int | `443` | HTTPS port for TCP passthrough |
| service.annotations | object | `{}` | Annotations for the service |
| haproxy-ingress.enabled | bool | `true` | Enable included HAProxy Ingress controller |
| haproxy-ingress.controller.ingressClass | string | `"haproxy"` | Ingress class name for the controller |
| haproxy-ingress.controller.ingressClassResource.enabled | bool | `true` | Enable IngressClass resource creation |
| haproxy-ingress.controller.ingressClassResource.name | string | `"haproxy"` | Name of the IngressClass resource |
| haproxy-ingress.controller.ingressClassResource.default | bool | `false` | Set as default IngressClass |
| haproxy-ingress.controller.service.type | string | `"LoadBalancer"` | HAProxy Ingress controller service type |
| haproxy-ingress.controller.extraArgs | list | `["--tcp-services-configmap=$(POD_NAMESPACE)/haproxy-tcp-services"]` | Extra arguments for HAProxy controller |
| haproxy-ingress.controller.config.ssl-redirect | string | `"false"` | Disable automatic SSL redirect |
| ingress.enabled | bool | `true` | Enable ingress resource |
| ingress.className | string | `"haproxy"` | Ingress class name |
| ingress.annotations | object | `{"kubernetes.io/ingress.class": "haproxy"}` | Annotations for the ingress resource |
| ingress.hosts[0].host | string | `"probo.example.com"` | Ingress hostname |
| ingress.hosts[0].paths[0].path | string | `"/"` | Path to match |
| ingress.hosts[0].paths[0].pathType | string | `"Prefix"` | Path matching type |
| ingress.tls | list | `[]` | TLS configuration for ingress |
| resources.limits.cpu | string | `"2000m"` | CPU limit for the container |
| resources.limits.memory | string | `"2Gi"` | Memory limit for the container |
| resources.requests.cpu | string | `"500m"` | CPU request for the container |
| resources.requests.memory | string | `"512Mi"` | Memory request for the container |
| livenessProbe.httpGet.path | string | `"/"` | Path for the liveness probe |
| livenessProbe.httpGet.port | string | `"http"` | Port for the liveness probe |
| livenessProbe.initialDelaySeconds | int | `30` | Initial delay for liveness probe |
| livenessProbe.periodSeconds | int | `10` | Period for liveness probe |
| livenessProbe.timeoutSeconds | int | `5` | Timeout for liveness probe |
| livenessProbe.failureThreshold | int | `6` | Failure threshold for liveness probe |
| readinessProbe.httpGet.path | string | `"/"` | Path for the readiness probe |
| readinessProbe.httpGet.port | string | `"http"` | Port for the readiness probe |
| readinessProbe.initialDelaySeconds | int | `10` | Initial delay for readiness probe |
| readinessProbe.periodSeconds | int | `5` | Period for readiness probe |
| readinessProbe.timeoutSeconds | int | `3` | Timeout for readiness probe |
| readinessProbe.failureThreshold | int | `3` | Failure threshold for readiness probe |
| autoscaling.enabled | bool | `false` | Enable horizontal pod autoscaling |
| autoscaling.minReplicas | int | `1` | Minimum number of replicas |
| autoscaling.maxReplicas | int | `10` | Maximum number of replicas |
| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage |
| volumes | list | `[]` | Additional volumes on the output Deployment definition |
| volumeMounts | list | `[]` | Additional volumeMounts on the output Deployment definition |
| nodeSelector | object | `{}` | Node selector for pod assignment |
| tolerations | list | `[]` | Tolerations for pod assignment |
| affinity | object | `{}` | Affinity rules for pod assignment |
| persistence.enabled | bool | `false` | Enable persistent volume for data storage |
| persistence.storageClass | string | `""` | Storage class for the persistent volume |
| persistence.accessMode | string | `"ReadWriteOnce"` | Access mode for the persistent volume |
| persistence.size | string | `"10Gi"` | Size of the persistent volume |
| persistence.annotations | object | `{}` | Annotations for the persistent volume claim |
| metrics.serviceMonitor.enabled | bool | `false` | Enable Prometheus Operator ServiceMonitor |
| metrics.serviceMonitor.interval | string | `"30s"` | Scrape interval for metrics |
| metrics.serviceMonitor.scrapeTimeout | string | `"10s"` | Scrape timeout for metrics |
| metrics.serviceMonitor.labels | object | `{}` | Labels for the ServiceMonitor |
| metrics.serviceMonitor.relabelings | list | `[]` | Relabeling configs for the ServiceMonitor |
| probo.hostname | string | `"probo.example.com"` | Public hostname where Probo will be accessible |
| probo.encryptionKey | string | `""` | **REQUIRED** Base64-encoded encryption key (generate with: openssl rand -base64 32) |
| probo.service.port | int | `8080` | Probo application service port |
| probo.metrics.port | int | `8081` | Probo metrics service port |
| probo.tracing.enabled | bool | `false` | Enable OpenTelemetry tracing |
| probo.tracing.addr | string | `""` | OTLP gRPC endpoint (e.g., tempo:4317) |
| probo.tracing.maxBatchSize | int | `512` | Maximum batch size for trace exports |
| probo.tracing.batchTimeout | int | `5` | Batch timeout in seconds |
| probo.tracing.exportTimeout | int | `30` | Export timeout in seconds |
| probo.tracing.maxQueueSize | int | `2048` | Maximum queue size for traces |
| probo.cors.allowedOrigins | list | `["https://probo.example.com", "http://..."]` | CORS allowed origins |
| probo.extraHeaderFields | object | `{}` | Extra HTTP headers to add to responses |
| probo.auth.disableSignup | bool | `false` | Disable user signup |
| probo.auth.invitationTokenValidity | int | `3600` | Invitation token validity in seconds |
| probo.auth.cookieName | string | `"SSID"` | Authentication cookie name |
| probo.auth.cookieDomain | string | `"probo.example.com"` | Authentication cookie domain |
| probo.auth.cookieSecret | string | `""` | **REQUIRED** Cookie signing secret (at least 32 bytes, generate with: openssl rand -base64 32) |
| probo.auth.cookieDuration | int | `24` | Cookie duration in hours |
| probo.auth.passwordPepper | string | `""` | **REQUIRED** Password hashing pepper (at least 32 bytes, generate with: openssl rand -base64 32) |
| probo.auth.passwordIterations | int | `1000000` | Password hashing iterations |
| probo.trustAuth.cookieName | string | `"TCT"` | Trust center cookie name |
| probo.trustAuth.cookieDomain | string | `"probo.example.com"` | Trust center cookie domain |
| probo.trustAuth.cookieDuration | int | `24` | Trust center cookie duration in hours |
| probo.trustAuth.tokenDuration | int | `168` | Trust token duration in hours |
| probo.trustAuth.reportUrlDuration | int | `15` | Report URL duration in minutes |
| probo.trustAuth.tokenSecret | string | `""` | **REQUIRED** Trust token signing secret (at least 32 bytes, generate with: openssl rand -base64 32) |
| probo.trustAuth.scope | string | `"trust_center_readonly"` | Trust token scope |
| probo.trustAuth.tokenType | string | `"trust_center_access"` | Trust token type |
| probo.mailer.senderName | string | `"Probo"` | Email sender name |
| probo.mailer.senderEmail | string | `"no-reply@notification.getprobo.com"` | Email sender address |
| probo.mailer.smtp.addr | string | `"sandbox.smtp.mailtrap.io:2525"` | SMTP server address |
| probo.mailer.smtp.user | string | `"2d1b1d0e8b3d0b"` | SMTP username |
| probo.mailer.smtp.password | string | `"25a8eb11e75e8d"` | SMTP password |
| probo.mailer.smtp.tlsRequired | bool | `true` | Require TLS for SMTP connection |
| probo.openai.apiKey | string | `""` | OpenAI API key for AI features (optional) |
| probo.openai.temperature | float | `0.1` | OpenAI temperature setting |
| probo.openai.modelName | string | `"gpt-4o"` | OpenAI model name |
| probo.customDomains.enabled | bool | `false` | Enable custom domains feature |
| probo.customDomains.renewalInterval | int | `3600` | Certificate renewal interval in seconds |
| probo.customDomains.provisionInterval | int | `30` | Domain provision interval in seconds |
| probo.customDomains.cnameTarget | string | `"probo.example.com"` | CNAME target for custom domains |
| probo.customDomains.acme.directory | string | `"https://acme-v02.api.letsencrypt.org/directory"` | ACME directory URL |
| probo.customDomains.acme.email | string | `"admin@example.com"` | ACME registration email |
| probo.customDomains.acme.keyType | string | `"EC256"` | ACME key type |
| probo.customDomains.acme.insecureTls | bool | `false` | Allow insecure TLS for ACME |
| probo.connectors | list | `[]` | External OAuth2 connectors configuration |
| postgresql.enabled | bool | `true` | Enable included PostgreSQL container for demo purposes using CloudNativePG |
| postgresql.resources.limits.memory | string | `"2Gi"` | PostgreSQL memory limit |
| postgresql.resources.limits.ephemeral-storage | string | `"5Gi"` | PostgreSQL ephemeral storage limit |
| postgresql.auth.postgresUser | string | `"probod"` | PostgreSQL username |
| postgresql.auth.postgresPassword | string | `"probod"` | PostgreSQL password |
| postgresql.auth.database | string | `"probod"` | PostgreSQL database name |
| s3.region | string | `"us-east-1"` | S3 region |
| s3.bucket | string | `"probod"` | S3 bucket name |
| s3.endpoint | string | `""` | S3 endpoint (leave empty for AWS S3, set for S3-compatible storage) |
| s3.accessKeyId | string | `""` | **REQUIRED** (when minio.enabled=false) S3 access key |
| s3.secretAccessKey | string | `""` | **REQUIRED** (when minio.enabled=false) S3 secret key |
| minio.enabled | bool | `true` | Enable included MinIO for demo purposes (NOT for production) |
| minio.mode | string | `"standalone"` | MinIO mode |
| minio.persistence.enabled | bool | `false` | Enable MinIO persistence |
| minio.resources.requests.memory | string | `"128Mi"` | MinIO memory request |
| minio.primary.enabled | bool | `true` | Enable MinIO primary node |
| minio.auth.rootUser | string | `"probod"` | MinIO root username |
| minio.auth.rootPassword | string | `"probod"` | MinIO root password |
| chrome.enabled | bool | `true` | Deploy Chrome headless in the cluster for PDF generation |
| chrome.replicaCount | int | `1` | Number of Chrome replicas |
| chrome.image.repository | string | `"chromedp/headless-shell"` | Chrome container image repository |
| chrome.image.tag | string | `"140.0.7259.2"` | Chrome image tag |
| chrome.image.pullPolicy | string | `"IfNotPresent"` | Chrome image pull policy |
| chrome.resources.limits.cpu | string | `"1000m"` | Chrome CPU limit |
| chrome.resources.limits.memory | string | `"1Gi"` | Chrome memory limit |
| chrome.resources.requests.cpu | string | `"100m"` | Chrome CPU request |
| chrome.resources.requests.memory | string | `"256Mi"` | Chrome memory request |
| chrome.external.addr | string | `""` | External Chrome address (used when chrome.enabled=false) |

View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@@ -0,0 +1,87 @@
# Configuration Note
## Environment Variable Substitution
The Helm chart templates use a `-from-env` suffix pattern in the configuration file (e.g., `encryption-key-from-env: "ENCRYPTION_KEY"`). This pattern assumes the Probo application supports reading secrets from environment variables instead of directly from the config file.
### Current Implementation
The chart currently:
1. Stores sensitive values in a Kubernetes Secret
2. Mounts them as environment variables in the pod
3. References them in the config with `-from-env` suffix
### If Probo Doesn't Support `-from-env` Pattern
If the current Probo application doesn't support the `-from-env` pattern, you have three options:
#### Option 1: Implement in Probo (Recommended)
Update the Probo configuration parser to recognize `-from-env` suffixes and read from environment variables. This is the most secure approach.
Example in Go:
```go
// In config loading code
if strings.HasSuffix(key, "-from-env") {
envVar := value.(string)
actualValue := os.Getenv(envVar)
// Use actualValue instead of value
}
```
#### Option 2: Use initContainer with envsubst
Modify the deployment to use an init container that substitutes environment variables:
```yaml
initContainers:
- name: config-init
image: alpine:3.19
command:
- sh
- -c
- |
apk add --no-cache gettext
envsubst < /config-template/config.yaml > /config/config.yaml
env:
# All environment variables from secrets
- name: ENCRYPTION_KEY
valueFrom: ...
volumeMounts:
- name: config-template
mountPath: /config-template
- name: config
mountPath: /config
```
Then update the ConfigMap to use `${ENCRYPTION_KEY}` instead of the `-from-env` pattern.
#### Option 3: Mount Everything as Secret
Create a complete config.yaml as a Secret (not ConfigMap) with actual values:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: probo-config
stringData:
config.yaml: |
probod:
encryption-key: {{ .Values.probo.encryptionKey }}
# ... all config with actual values
```
This is simpler but less secure as the entire config is stored as a Secret.
## Recommendation
We recommend implementing Option 1 in the Probo codebase as it:
- Follows Kubernetes best practices
- Keeps secrets separate from configuration
- Works well with secret management tools (Vault, External Secrets, etc.)
- Supports secret rotation without config changes
## Current Status
The Helm chart is built with Option 1 in mind. If the Probo application doesn't yet support `-from-env`, please implement it or use one of the alternative approaches above.

View File

@@ -0,0 +1,9 @@
dependencies:
- name: minio
repository: https://charts.min.io/
version: 5.4.0
- name: kubernetes-ingress
repository: https://haproxytech.github.io/helm-charts
version: 1.45.1
digest: sha256:86861ee988df3673c6306ff42719f09d12ce22e2d48a0db757ad4365d204bd85
generated: "2025-10-31T10:17:36.108938+01:00"

View File

@@ -0,0 +1,29 @@
apiVersion: v2
name: probo
description: A Helm chart for deploying Probo - Open Source Compliance Platform
type: application
version: 0.1.0
appVersion: "0.74.7"
keywords:
- compliance
- soc2
- security
- governance
home: https://getprobo.com
sources:
- https://github.com/getprobo/probo
maintainers:
- name: Probo Team
email: hello@getprobo.com
icon: https://github.com/getprobo/probo/blob/main/.github/cover_v3.png
dependencies:
- name: minio
condition: minio.enabled
repository: https://charts.min.io/
version: 5.4.0 # Latest stable version
- name: kubernetes-ingress
alias: haproxy-ingress
condition: haproxy-ingress.enabled
version: 1.45.1
repository: https://haproxytech.github.io/helm-charts

View File

@@ -0,0 +1,267 @@
# 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, MinIO, etc.)
## Installing the Chart
### Generate Required Secrets
```bash
# 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)
echo "Save these secrets securely!"
```
### Install
```bash
helm install probo . \
--set probo.hostname="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 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:
```yaml
# values-production.yaml
image:
repository: ghcr.io/getprobo/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:
hostname: "probo.example.com"
encryptionKey: "<secret>"
cors:
allowedOrigins:
- "https://probo.example.com"
auth:
cookieDomain: "example.com"
cookieSecret: "<secret>"
passwordPepper: "<secret>"
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:
```bash
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) |
| `postgresql.host` | PostgreSQL hostname |
| `postgresql.password` | PostgreSQL password |
| `s3.accessKeyId` | S3 access key ID |
| `s3.secretAccessKey` | S3 secret access key |
### Key Configuration Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `image.repository` | Probo image repository | `ghcr.io/getprobo/probo` |
| `image.tag` | Probo image tag | Chart appVersion |
| `replicaCount` | Number of Probo replicas | `1` |
| `probo.hostname` | Public hostname | `probo.example.com` |
| `postgresql.host` | PostgreSQL host | `""` (required) |
| `postgresql.port` | PostgreSQL port | `5432` |
| `postgresql.database` | Database name | `probod` |
| `postgresql.username` | Database user | `probod` |
| `s3.bucket` | S3 bucket name | `probod` |
| `s3.region` | AWS region | `us-east-1` |
| `s3.endpoint` | S3 endpoint (for S3-compatible) | `""` |
| `chrome.enabled` | Deploy Chrome | `true` |
| `chrome.external.addr` | External Chrome (if disabled) | `""` |
| `ingress.enabled` | Enable ingress | `false` |
See [values.yaml](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.
### Backup
Use your PostgreSQL provider's backup solution (e.g., AWS RDS automated backups, GCP Cloud SQL backups).
## Upgrading
```bash
helm upgrade probo . -f values-production.yaml
```
## Uninstalling
```bash
helm uninstall probo
```
**Note:** This does not delete your external PostgreSQL database or S3 bucket.
## Troubleshooting
### View Logs
```bash
kubectl logs -f deployment/probo
```
### Check Configuration
```bash
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.
## Examples
### AWS Deployment
```yaml
postgresql:
host: "mydb.abc123.us-east-1.rds.amazonaws.com"
password: "<rds-password>"
s3:
region: "us-east-1"
bucket: "my-probo-bucket"
accessKeyId: "<aws-key>"
secretAccessKey: "<aws-secret>"
# Leave endpoint empty for AWS S3
```
### GCP Deployment
```yaml
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
```yaml
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:
```yaml
chrome:
enabled: false
external:
addr: "chrome.browserless.io:3000"
```

View File

@@ -0,0 +1,84 @@
🎉 Probo has been successfully deployed!
📋 Release Information:
Name: {{ .Release.Name }}
Namespace: {{ .Release.Namespace }}
Version: {{ .Chart.AppVersion }}
🌐 Accessing Probo:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
Application: {{ if $.Values.ingress.tls }}https{{ else }}http{{ end }}://{{ $host.host }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
Get the application URL by running:
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "probo.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo "Application: http://$NODE_IP:$NODE_PORT"
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status by running:
kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "probo.fullname" . }}
Once available, get the application URL by running:
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "probo.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo "Application: http://$SERVICE_IP:{{ .Values.service.port }}"
{{- else if contains "ClusterIP" .Values.service.type }}
Application is available only within the cluster.
To access it from your local machine, run:
kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "probo.fullname" . }} 8080:{{ .Values.service.port }}
Then visit: http://localhost:8080
{{- end }}
📊 Monitoring:
Metrics endpoint: http://{{ include "probo.fullname" . }}:{{ .Values.probo.metrics.port }}/metrics
To access metrics from your local machine, run:
kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "probo.fullname" . }} 8081:{{ .Values.probo.metrics.port }}
🔍 View Logs:
kubectl --namespace {{ .Release.Namespace }} logs -f deployment/{{ include "probo.fullname" . }}
🗄️ Database:
Using external PostgreSQL at: {{ .Values.postgresql.host }}:{{ .Values.postgresql.port }}
Database: {{ .Values.postgresql.database }}
💾 Object Storage:
Using S3 storage
Bucket: {{ .Values.s3.bucket }}
Region: {{ .Values.s3.region }}
{{- if .Values.s3.endpoint }}
Endpoint: {{ .Values.s3.endpoint }}
{{- end }}
⚠️ Important Security Notes:
{{- if not .Values.ingress.tls }}
⚠️ TLS is not enabled! Enable it for production by configuring ingress.tls
{{- end }}
📚 Next Steps:
1. Update the hostname in values.yaml to match your domain:
probo.hostname: "{{ .Values.probo.hostname }}"
2. Configure email delivery:
Update probo.mailer.smtp settings with your SMTP server details.
{{- if not .Values.probo.openai.apiKey }}
3. (Optional) Configure OpenAI integration for AI-powered features:
Set probo.openai.apiKey in values.yaml
{{- end }}
4. Review and update CORS allowed origins:
probo.cors.allowedOrigins
📖 Documentation:
- GitHub: https://github.com/getprobo/probo
- Discord: https://discord.gg/8qfdJYfvpY
- Website: https://getprobo.com
✨ Thank you for using Probo!

View File

@@ -0,0 +1,159 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "probo.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "probo.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "probo.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "probo.labels" -}}
helm.sh/chart: {{ include "probo.chart" . }}
{{ include "probo.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "probo.selectorLabels" -}}
app.kubernetes.io/name: {{ include "probo.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "probo.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "probo.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
PostgreSQL hostname
*/}}
{{- define "probo.postgresql.host" -}}
{{- if .Values.postgresql.enabled }}
{{- printf "%s-postgresql-demo" .Release.Name }}
{{- else }}
{{- .Values.postgresql.host | required "postgresql.host is required when postgresql.enabled=false" }}
{{- end }}
{{- end }}
{{/*
PostgreSQL port
*/}}
{{- define "probo.postgresql.port" -}}
{{- if .Values.postgresql.enabled }}
{{- 5432 }}
{{- else }}
{{- .Values.postgresql.port | default 5432 }}
{{- end }}
{{- end }}
{{/*
PostgreSQL database name
*/}}
{{- define "probo.postgresql.database" -}}
{{- if .Values.postgresql.enabled }}
{{- .Values.postgresql.auth.database | default "probod" }}
{{- else }}
{{- .Values.postgresql.database | default "probod" }}
{{- end }}
{{- end }}
{{/*
PostgreSQL username
*/}}
{{- define "probo.postgresql.username" -}}
{{- if .Values.postgresql.enabled }}
{{- .Values.postgresql.auth.postgresUser | default "probod" }}
{{- else }}
{{- .Values.postgresql.username | default "probod" }}
{{- end }}
{{- end }}
{{/*
PostgreSQL password (from subchart or external config)
*/}}
{{- define "probo.postgresql.password" -}}
{{- if .Values.postgresql.enabled }}
{{- .Values.postgresql.auth.postgresPassword | required "postgresql.auth.postgresPassword is required when postgresql.enabled=true" }}
{{- else }}
{{- .Values.postgresql.password | required "postgresql.password is required when postgresql.enabled=false" }}
{{- end }}
{{- end }}
{{/*
S3 endpoint
*/}}
{{- define "probo.s3.endpoint" -}}
{{- if .Values.minio.enabled }}
{{- printf "http://%s-minio:9000" .Release.Name }}
{{- else }}
{{- .Values.s3.endpoint }}
{{- end }}
{{- end }}
{{/*
S3 access key
*/}}
{{- define "probo.s3.accessKeyId" -}}
{{- if .Values.minio.enabled }}
{{- .Values.minio.auth.rootUser | required "minio.auth.rootUser is required when minio.enabled=true" }}
{{- else }}
{{- .Values.s3.accessKeyId | required "s3.accessKeyId is required when minio.enabled=false" }}
{{- end }}
{{- end }}
{{/*
S3 secret key
*/}}
{{- define "probo.s3.secretAccessKey" -}}
{{- if .Values.minio.enabled }}
{{- .Values.minio.auth.rootPassword | required "minio.auth.rootPassword is required when minio.enabled=true" }}
{{- else }}
{{- .Values.s3.secretAccessKey | required "s3.secretAccessKey is required when minio.enabled=false" }}
{{- end }}
{{- end }}
{{/*
Chrome DevTools Protocol address
*/}}
{{- define "probo.chrome.addr" -}}
{{- if .Values.chrome.enabled }}
{{- printf "%s-chrome:9222" (include "probo.fullname" .) }}
{{- else }}
{{- .Values.chrome.external.addr | required "chrome.external.addr is required when chrome.enabled=false" }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,77 @@
{{- if .Values.chrome.enabled }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "probo.fullname" . }}-chrome
labels:
{{- include "probo.labels" . | nindent 4 }}
app.kubernetes.io/component: chrome
spec:
replicas: {{ .Values.chrome.replicaCount }}
selector:
matchLabels:
{{- include "probo.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: chrome
template:
metadata:
labels:
{{- include "probo.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: chrome
spec:
containers:
- name: chrome
image: "{{ .Values.chrome.image.repository }}:{{ .Values.chrome.image.tag }}"
imagePullPolicy: {{ .Values.chrome.image.pullPolicy }}
args:
- --headless
- --disable-gpu
- --disable-dev-shm-usage
- --hide-scrollbars
- --mute-audio
- --no-default-browser-check
- --no-first-run
- --disable-background-networking
- --disable-background-timer-throttling
- --disable-extensions
ports:
- name: devtools
containerPort: 9222
protocol: TCP
livenessProbe:
httpGet:
path: /json/version
port: devtools
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /json/version
port: devtools
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.chrome.resources | nindent 12 }}
securityContext:
capabilities:
add:
- SYS_ADMIN
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "probo.fullname" . }}-chrome
labels:
{{- include "probo.labels" . | nindent 4 }}
app.kubernetes.io/component: chrome
spec:
type: ClusterIP
ports:
- port: 9222
targetPort: devtools
protocol: TCP
name: devtools
selector:
{{- include "probo.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: chrome
{{- end }}

View File

@@ -0,0 +1,119 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "probo.fullname" . }}
labels:
{{- include "probo.labels" . | nindent 4 }}
data:
config.yml: |
unit:
metrics:
addr: "0.0.0.0:{{ .Values.probo.metrics.port }}"
{{- if .Values.probo.tracing.enabled }}
tracing:
addr: {{ .Values.probo.tracing.addr | quote }}
max-batch-size: {{ .Values.probo.tracing.maxBatchSize }}
batch-timeout: {{ .Values.probo.tracing.batchTimeout }}
export-timeout: {{ .Values.probo.tracing.exportTimeout }}
max-queue-size: {{ .Values.probo.tracing.maxQueueSize }}
{{- end }}
probod:
hostname: {{ .Values.probo.hostname | quote }}
encryption-key: {{ .Values.probo.encryptionKey | quote }}
chrome-dp-addr: {{ include "probo.chrome.addr" . | quote }}
api:
addr: "0.0.0.0:{{ .Values.service.port }}"
cors:
allowed-origins:
{{- toYaml .Values.probo.cors.allowedOrigins | nindent 12 }}
{{- if .Values.probo.extraHeaderFields }}
extra-header-fields:
{{- toYaml .Values.probo.extraHeaderFields | nindent 10 }}
{{- end }}
pg:
addr: "{{ include "probo.postgresql.host" . }}:{{ include "probo.postgresql.port" . }}"
username: {{ include "probo.postgresql.username" . | quote }}
password: {{ include "probo.postgresql.password" . | quote }}
database: {{ include "probo.postgresql.database" . | quote }}
pool-size: {{ .Values.postgresql.poolSize }}
{{- if .Values.postgresql.caCertBundle }}
ca-cert-bundle: |
{{- .Values.postgresql.caCertBundle | nindent 10 }}
{{- end }}
auth:
disable-signup: {{ .Values.probo.auth.disableSignup }}
invitation-confirmation-token-validity: {{ .Values.probo.auth.invitationTokenValidity }}
cookie:
name: {{ .Values.probo.auth.cookieName | quote }}
domain: {{ .Values.probo.auth.cookieDomain | quote }}
secret: {{ .Values.probo.auth.cookieSecret | quote }}
duration: {{ .Values.probo.auth.cookieDuration }}
password:
pepper: {{ .Values.probo.auth.passwordPepper | quote }}
iterations: {{ .Values.probo.auth.passwordIterations }}
trust-auth:
cookie-name: {{ .Values.probo.trustAuth.cookieName | quote }}
cookie-domain: {{ .Values.probo.trustAuth.cookieDomain | quote }}
cookie-duration: {{ .Values.probo.trustAuth.cookieDuration }}
token-duration: {{ .Values.probo.trustAuth.tokenDuration }}
report-url-duration: {{ .Values.probo.trustAuth.reportUrlDuration }}
{{- if .Values.probo.trustAuth.ports }}
ports:
http-challenge: {{ .Values.probo.trustAuth.ports.httpChallenge }}
tls-http-server: {{ .Values.probo.trustAuth.ports.tlsHttpServer }}
{{- end }}
token-secret: {{ .Values.probo.trustAuth.tokenSecret | quote }}
scope: {{ .Values.probo.trustAuth.scope | quote }}
token-type: {{ .Values.probo.trustAuth.tokenType | quote }}
aws:
region: {{ .Values.s3.region | quote }}
bucket: {{ .Values.s3.bucket | quote }}
access-key-id: {{ .Values.s3.accessKey | quote }}
secret-access-key: {{ .Values.s3.secretKey | quote }}
{{- $s3Endpoint := include "probo.s3.endpoint" . }}
{{- if $s3Endpoint }}
endpoint: {{ $s3Endpoint | quote }}
{{- end }}
mailer:
sender-name: {{ .Values.probo.mailer.senderName | quote }}
sender-email: {{ .Values.probo.mailer.senderEmail | quote }}
smtp:
addr: {{ .Values.probo.mailer.smtp.addr | quote }}
{{- if .Values.probo.mailer.smtp.user }}
user: {{ .Values.probo.mailer.smtp.user | quote }}
{{- end }}
{{- if .Values.probo.mailer.smtp.password }}
password: {{ .Values.probo.mailer.smtp.password | quote }}
{{- end }}
tls-required: {{ .Values.probo.mailer.smtp.tlsRequired }}
{{- if .Values.probo.openai.apiKey }}
openai:
api-key-from-env: "OPENAI_API_KEY"
temperature: {{ .Values.probo.openai.temperature }}
model-name: {{ .Values.probo.openai.modelName | quote }}
{{- end }}
{{- if .Values.probo.customDomains.enabled }}
custom-domains:
renewal-interval: {{ .Values.probo.customDomains.renewalInterval }}
provision-interval: {{ .Values.probo.customDomains.provisionInterval }}
cname-target: {{ .Values.probo.customDomains.cnameTarget | quote }}
acme:
directory: {{ .Values.probo.customDomains.acme.directory | quote }}
email: {{ .Values.probo.customDomains.acme.email | quote }}
key-type: {{ .Values.probo.customDomains.acme.keyType | quote }}
insecure-tls: {{ .Values.probo.customDomains.acme.insecureTls }}
{{- end }}
{{- if .Values.probo.connectors }}
connectors:
{{- toYaml .Values.probo.connectors | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,162 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "probo.fullname" . }}
labels:
{{- include "probo.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "probo.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "probo.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "probo.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["/bin/sh", "-c"] # replaces ENTRYPOINT
args: ["echo $DB_PASSWORD && cat /etc/probod/config.yml && exec /usr/local/bin/probod -cfg-file /etc/probod/config.yml"]
# args:
# - "-cfg-file"
# - "/etc/probod/config.yml"
env:
# Database credentials
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "probo.fullname" . }}
key: db-password
# S3 credentials
- name: S3_ACCESS_KEY
valueFrom:
secretKeyRef:
name: {{ include "probo.fullname" . }}
key: s3-access-key
- name: S3_SECRET_KEY
valueFrom:
secretKeyRef:
name: {{ include "probo.fullname" . }}
key: s3-secret-key
# Application secrets
- name: ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: {{ include "probo.fullname" . }}
key: encryption-key
- name: COOKIE_SECRET
valueFrom:
secretKeyRef:
name: {{ include "probo.fullname" . }}
key: cookie-secret
- name: PASSWORD_PEPPER
valueFrom:
secretKeyRef:
name: {{ include "probo.fullname" . }}
key: password-pepper
- name: TRUST_TOKEN_SECRET
valueFrom:
secretKeyRef:
name: {{ include "probo.fullname" . }}
key: trust-token-secret
# Optional SMTP credentials
{{- if .Values.probo.mailer.smtp.user }}
- name: SMTP_USER
valueFrom:
secretKeyRef:
name: {{ include "probo.fullname" . }}
key: smtp-user
{{- end }}
{{- if .Values.probo.mailer.smtp.password }}
- name: SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "probo.fullname" . }}
key: smtp-password
{{- end }}
# Optional OpenAI API key
{{- if .Values.probo.openai.apiKey }}
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: {{ include "probo.fullname" . }}
key: openai-api-key
{{- end }}
ports:
- name: http
containerPort: {{ .Values.service.httpPort }}
protocol: TCP
- name: https
containerPort: {{ .Values.service.httpsPort }}
protocol: TCP
- name: backoffice
containerPort: {{ .Values.service.port }}
protocol: TCP
- name: metrics
containerPort: {{ .Values.probo.metrics.port }}
protocol: TCP
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: config
mountPath: /etc/probod
readOnly: false
- name: data
mountPath: /data
{{- with .Values.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumes:
- name: config
configMap:
name: {{ include "probo.fullname" . }}
- name: data
{{- if .Values.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ .Values.persistence.existingClaim | default (include "probo.fullname" .) }}
{{- else }}
emptyDir: {}
{{- end }}
{{- with .Values.volumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,16 @@
{{- if index .Values "haproxy-ingress" "enabled" -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: haproxy-tcp-services
namespace: {{ .Release.Namespace }}
labels:
{{- include "probo.labels" . | nindent 4 }}
data:
# TCP passthrough for port 80 (HTTP)
# Format: "<port>": "<namespace>/<service>:<port>"
"80": "{{ .Release.Namespace }}/{{ include "probo.fullname" . }}:{{ .Values.service.httpPort }}"
# TCP passthrough for port 443 (HTTPS)
"443": "{{ .Release.Namespace }}/{{ include "probo.fullname" . }}:{{ .Values.service.httpsPort }}"
{{- end }}

View File

@@ -0,0 +1,32 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "probo.fullname" . }}
labels:
{{- include "probo.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "probo.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,41 @@
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "probo.fullname" . }}-http
labels:
{{- include "probo.labels" . | nindent 4 }}
annotations:
{{- with .Values.ingress.annotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "probo.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,81 @@
{{- if .Values.postgresql.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "probo.fullname" . }}-postgresql-demo
spec:
replicas: 1
selector:
matchLabels:
app: {{ include "probo.fullname" . }}-postgresql-demo
template:
metadata:
labels:
app: {{ include "probo.fullname" . }}-postgresql-demo
spec:
containers:
- name: postgres
image: postgres:18
env:
- name: POSTGRES_USER
value: {{ .Values.postgresql.auth.postgresUser }}
- name: POSTGRES_PASSWORD
value: {{ .Values.postgresql.auth.postgresPassword }}
- name: POSTGRES_DB
value: {{ .Values.postgresql.auth.database }}
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-init
mountPath: /docker-entrypoint-initdb.d
readOnly: true
resources:
{{- toYaml .Values.postgresql.resources | nindent 12 }}
volumes:
- name: postgres-init
configMap:
name: {{ include "probo.fullname" . }}-postgres-init
defaultMode: 0755
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-postgresql-demo
spec:
selector:
app: {{ include "probo.fullname" . }}-postgresql-demo
ports:
- port: 5432
targetPort: 5432
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "probo.fullname" . }}-postgres-init
labels:
{{- include "probo.labels" . | nindent 4 }}
data:
01_probod.sh: |
#!/bin/sh
set -eu
psql -v ON_ERROR_STOP=1 -U $POSTGRES_USER <<-EOF
ALTER USER probod WITH SUPERUSER;
ALTER USER probod PASSWORD 'probod';
GRANT ALL PRIVILEGES ON DATABASE probod TO probod;
CREATE DATABASE probod_test;
GRANT ALL PRIVILEGES ON DATABASE probod_test TO probod;
EOF
psql -v ON_ERROR_STOP=1 -U $POSTGRES_USER -d probod <<-EOF
ALTER SCHEMA public OWNER TO probod;
GRANT ALL ON SCHEMA public TO probod;
EOF
psql -v ON_ERROR_STOP=1 -U $POSTGRES_USER -d probod_test <<-EOF
ALTER SCHEMA public OWNER TO probod;
GRANT ALL ON SCHEMA public TO probod;
EOF
---
{{- end -}}

View File

@@ -0,0 +1,25 @@
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "probo.fullname" . }}
labels:
{{- include "probo.labels" . | nindent 4 }}
{{- with .Values.persistence.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
accessModes:
- {{ .Values.persistence.accessMode | quote }}
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- if .Values.persistence.storageClass }}
{{- if (eq "-" .Values.persistence.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: {{ .Values.persistence.storageClass | quote }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,33 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ include "probo.fullname" . }}
labels:
{{- include "probo.labels" . | nindent 4 }}
type: Opaque
stringData:
# Database credentials
db-password: {{ include "probo.postgresql.password" . | quote }}
# S3 credentials
s3-access-key: {{ include "probo.s3.accessKeyId" . | quote }}
s3-secret-key: {{ include "probo.s3.secretAccessKey" . | quote }}
# Application secrets
encryption-key: {{ .Values.probo.encryptionKey | required "probo.encryptionKey is required" | quote }}
cookie-secret: {{ .Values.probo.auth.cookieSecret | required "probo.auth.cookieSecret is required" | quote }}
password-pepper: {{ .Values.probo.auth.passwordPepper | required "probo.auth.passwordPepper is required" | quote }}
trust-token-secret: {{ .Values.probo.trustAuth.tokenSecret | required "probo.trustAuth.tokenSecret is required" | quote }}
# Optional SMTP credentials
{{- if .Values.probo.mailer.smtp.user }}
smtp-user: {{ .Values.probo.mailer.smtp.user | quote }}
{{- end }}
{{- if .Values.probo.mailer.smtp.password }}
smtp-password: {{ .Values.probo.mailer.smtp.password | quote }}
{{- end }}
# Optional OpenAI API key
{{- if .Values.probo.openai.apiKey }}
openai-api-key: {{ .Values.probo.openai.apiKey | quote }}
{{- end }}

View File

@@ -0,0 +1,44 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "probo.fullname" . }}
labels:
{{- include "probo.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
{{- if and .Values.service.clusterIP (eq .Values.service.type "ClusterIP") }}
clusterIP: {{ .Values.service.clusterIP }}
{{- end }}
{{- if .Values.service.loadBalancerIP }}
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
{{- end }}
{{- if .Values.service.loadBalancerSourceRanges }}
loadBalancerSourceRanges:
{{- toYaml .Values.service.loadBalancerSourceRanges | nindent 4 }}
{{- end }}
ports:
- port: {{ .Values.service.httpPort }}
targetPort: http
protocol: TCP
name: http
- port: {{ .Values.service.httpsPort }}
targetPort: https
protocol: TCP
name: https
- port: {{ .Values.service.port }}
targetPort: backoffice
protocol: TCP
name: backoffice
{{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }}
nodePort: {{ .Values.service.nodePort }}
{{- end }}
- port: {{ .Values.probo.metrics.port }}
targetPort: metrics
protocol: TCP
name: metrics
selector:
{{- include "probo.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "probo.serviceAccountName" . }}
labels:
{{- include "probo.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- end }}

View File

@@ -0,0 +1,24 @@
{{- if .Values.metrics.serviceMonitor.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "probo.fullname" . }}
labels:
{{- include "probo.labels" . | nindent 4 }}
{{- with .Values.metrics.serviceMonitor.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
matchLabels:
{{- include "probo.selectorLabels" . | nindent 6 }}
endpoints:
- port: metrics
path: /metrics
interval: {{ .Values.metrics.serviceMonitor.interval }}
scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }}
{{- with .Values.metrics.serviceMonitor.relabelings }}
relabelings:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,311 @@
# Production values for probo.
# This file provides a template for production deployment.
# Copy this file to values-production.yaml and customize it for your environment.
# Use specific version tag
image:
repository: ghcr.io/getprobo/probo
tag: "0.74.7"
pullPolicy: IfNotPresent
# Scale for high availability
replicaCount: 3
# Production security context (non-root user)
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000 # probo user (UID defined in Dockerfile)
runAsGroup: 1000
fsGroup: 1000
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false # /data directory requires write access
runAsUser: 1000
runAsGroup: 1000
# Service configuration
service:
type: ClusterIP
port: 8080
httpPort: 80
httpsPort: 443
annotations: {}
# Production resource limits
resources:
limits:
cpu: 2000m
memory: 4Gi
requests:
cpu: 1000m
memory: 2Gi
# Enable horizontal pod autoscaling
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
# HAProxy Ingress Controller configuration
haproxy-ingress:
enabled: true
controller:
ingressClass: haproxy
ingressClassResource:
enabled: true
name: haproxy
default: false
name: controller
service:
type: LoadBalancer
annotations:
# Cloud provider annotations for LoadBalancer
# AWS example:
# service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
# GCP example:
# cloud.google.com/load-balancer-type: "External"
extraArgs:
- --tcp-services-configmap=$(POD_NAMESPACE)/haproxy-tcp-services
config:
ssl-redirect: "false" # Probo handles SSL internally
# Configure ingress with TLS
ingress:
enabled: true
className: "haproxy"
annotations:
kubernetes.io/ingress.class: haproxy
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 application configuration
probo:
# Must match your ingress hostname
hostname: "probo.example.com"
# REQUIRED: Generate with: openssl rand -base64 32
encryptionKey: "CHANGE_ME_ENCRYPTION_KEY"
service:
port: 8080
metrics:
port: 8081
# CORS configuration - add all domains that will access the API
cors:
allowedOrigins:
- "https://probo.example.com"
# Extra HTTP headers to add to responses
extraHeaderFields: {}
# X-Custom-Header: "custom-value"
# Authentication
auth:
# Set to true to disable public signup
disableSignup: false
# Cookie domain should be your root domain
cookieDomain: "example.com"
# REQUIRED: Generate with: openssl rand -base64 32
cookieSecret: "CHANGE_ME_COOKIE_SECRET"
# REQUIRED: Generate with: openssl rand -base64 32
passwordPepper: "CHANGE_ME_PASSWORD_PEPPER"
# Trust center authentication
trustAuth:
cookieName: "TCT"
cookieDomain: "example.com"
cookieDuration: 24
tokenDuration: 168
reportUrlDuration: 15
ports:
httpChallenge: 80
tlsHttpServer: 443
# REQUIRED: Generate with: openssl rand -base64 32
tokenSecret: "CHANGE_ME_TRUST_TOKEN_SECRET"
scope: "trust_center_readonly"
tokenType: "trust_center_access"
# Email configuration
mailer:
senderName: "Probo"
senderEmail: "noreply@example.com"
smtp:
# Use your SMTP provider (e.g., SendGrid, Mailgun, AWS SES)
addr: "smtp.sendgrid.net:587"
user: "apikey"
password: "CHANGE_ME_SMTP_PASSWORD"
tlsRequired: true
# OpenAI integration for AI features (optional)
openai:
apiKey: "CHANGE_ME_OPENAI_API_KEY"
temperature: 0.1
modelName: "gpt-4o"
# OpenTelemetry tracing (optional)
tracing:
enabled: true
addr: "tempo.monitoring:4317"
maxBatchSize: 1000
batchTimeout: 10
exportTimeout: 30
maxQueueSize: 10000
# Custom domains with Let's Encrypt (optional)
customDomains:
enabled: true
renewalInterval: 86400 # 24 hours
provisionInterval: 300 # 5 minutes
cnameTarget: "custom.example.com"
acme:
directory: "https://acme-v02.api.letsencrypt.org/directory"
email: "admin@example.com"
keyType: "EC256"
insecureTls: false
# External OAuth2 connectors (optional)
connectors:
- name: "github"
type: "oauth2"
config:
client-id: "CHANGE_ME_GITHUB_CLIENT_ID"
client-secret: "CHANGE_ME_GITHUB_CLIENT_SECRET"
redirect-uri: "https://probo.example.com/api/console/v1/connectors/complete"
auth-url: "https://github.com/login/oauth/authorize"
token-url: "https://github.com/login/oauth/access_token"
# PostgreSQL configuration (REQUIRED)
# For production: Use external managed PostgreSQL (AWS RDS, GCP Cloud SQL, etc.)
# Disable the included PostgreSQL chart
postgresql:
# Disable the included PostgreSQL for production
enabled: false
# External PostgreSQL connection (configure these when enabled=false)
# Examples:
# - AWS RDS: mydb.abc123.us-east-1.rds.amazonaws.com
# - GCP Cloud SQL: 10.0.0.5 (private IP)
# - Azure Database: myserver.postgres.database.azure.com
# - DigitalOcean: db-postgresql-nyc1-12345.ondigitalocean.com
host: "CHANGE_ME_POSTGRES_HOST"
port: 5432
database: "probod"
username: "probod"
password: "CHANGE_ME_DB_PASSWORD"
poolSize: 200
# Uncomment if using TLS with custom CA
# caCertBundle: |
# -----BEGIN CERTIFICATE-----
# ...
# -----END CERTIFICATE-----
# MinIO configuration
# Disable included MinIO for production - use external S3 instead
minio:
enabled: false
# S3 storage configuration (REQUIRED)
s3:
# AWS S3 example:
region: "us-east-1"
bucket: "probo-production"
# Leave endpoint empty for AWS S3
endpoint: ""
accessKeyId: "CHANGE_ME_AWS_ACCESS_KEY_ID"
secretAccessKey: "CHANGE_ME_AWS_SECRET_ACCESS_KEY"
# GCP Cloud Storage example (uncomment and adjust):
# region: "us-east1"
# bucket: "probo-production"
# endpoint: "https://storage.googleapis.com"
# accessKeyId: "GOOG1E..." # HMAC access key
# secretAccessKey: "..." # HMAC secret
# DigitalOcean Spaces example (uncomment and adjust):
# region: "nyc3"
# bucket: "probo-production"
# endpoint: "https://nyc3.digitaloceanspaces.com"
# accessKeyId: "..." # Spaces access key
# secretAccessKey: "..." # Spaces secret
# Chrome for PDF generation
chrome:
enabled: true
# Scale for concurrent PDF generation
replicaCount: 3
resources:
limits:
cpu: 1000m
memory: 2Gi
requests:
cpu: 500m
memory: 1Gi
# To use external Chrome service instead, set enabled=false and configure:
# external:
# addr: "chrome.browserless.io:3000"
# Persistent data volume (optional)
persistence:
enabled: true
# Use existing PVC or let Helm create one
# existingClaim: "probo-data-pvc"
storageClass: "" # Use default storage class or specify one
accessMode: ReadWriteOnce
size: 50Gi
annotations: {}
# Enable Prometheus monitoring
metrics:
serviceMonitor:
enabled: true
interval: 30s
scrapeTimeout: 10s
labels:
prometheus: kube-prometheus
# Pod placement (optional)
nodeSelector: {}
# node-role.kubernetes.io/worker: "true"
tolerations: []
# Anti-affinity for high availability
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- probo
topologyKey: kubernetes.io/hostname
# Service account annotations (for workload identity, IRSA, etc.)
serviceAccount:
create: true
annotations: {}
# AWS EKS example (uncomment and adjust):
# eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/probo-role
# GCP GKE example (uncomment and adjust):
# iam.gke.io/gcp-service-account: probo@PROJECT_ID.iam.gserviceaccount.com

View File

@@ -0,0 +1,352 @@
# Default values for probo.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: ghcr.io/getprobo/probo
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: "latest"
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podLabels: {}
podSecurityContext:
runAsUser: 0
runAsGroup: 0
# runAsNonRoot: false
# runAsUser: 1000 # probo user (UID defined in Dockerfile)
# fsGroup: 1000 # probo group
securityContext:
runAsUser: 0
runAsGroup: 0
privileged: true
# allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false # /data directory requires write access for file uploads
service:
type: ClusterIP
port: 8080
httpPort: 80
httpsPort: 443
# nodePort: 30080
# clusterIP: ""
# loadBalancerIP: ""
# loadBalancerSourceRanges: []
annotations: {}
haproxy-ingress:
enabled: true
controller:
ingressClass: haproxy # This tells the controller to create an IngressClass
ingressClassResource:
enabled: true # Enable IngressClass resource creation
name: haproxy
default: false
name: controller
service:
type: LoadBalancer
annotations: {}
extraArgs:
- --tcp-services-configmap=$(POD_NAMESPACE)/haproxy-tcp-services
config:
# HAProxy Ingress global config
ssl-redirect: "false"
ingress:
enabled: true
className: "haproxy"
annotations:
kubernetes.io/ingress.class: haproxy
# cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: probo.example.com
paths:
- path: /
pathType: Prefix
backend:
service:
name: probo
port:
number: 8080
tls: []
# - secretName: probo-tls
# hosts:
# - probo.example.com
resources:
limits:
cpu: 2000m
memory: 2Gi
requests:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /
port: backoffice
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
readinessProbe:
httpGet:
path: /
port: backoffice
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
# mountPath: "/etc/foo"
# readOnly: true
nodeSelector: {}
tolerations: []
affinity: {}
# Persistent data volume
persistence:
enabled: false
# existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 10Gi
annotations: {}
# Prometheus Operator ServiceMonitor
metrics:
serviceMonitor:
enabled: false
interval: 30s
scrapeTimeout: 10s
labels: {}
relabelings: []
# Probo application configuration
probo:
# Public hostname where Probo will be accessible
# This should match your ingress host
hostname: probo.example.com
# Base64-encoded encryption key (REQUIRED)
# Generate with: openssl rand -base64 32
encryptionKey: ""
service:
port: 8080
metrics:
port: 8081
# OpenTelemetry tracing configuration
tracing:
enabled: false
# OTLP gRPC endpoint (e.g., tempo:4317)
addr: ""
maxBatchSize: 512
batchTimeout: 5
exportTimeout: 30
maxQueueSize: 2048
# pg:
# addr: "probo-postgresql-demo-app:5432"
# username: "postgres"
# password: "probod"
# database: "probod"
# pool-size: 100
# CORS configuration
cors:
allowedOrigins:
- "https://probo.example.com"
- "http://probo.example.com"
# Extra HTTP headers to add to responses
extraHeaderFields: {}
# Authentication configuration
auth:
disableSignup: false
invitationTokenValidity: 3600
cookieName: "SSID"
cookieDomain: "probo.example.com"
# Cookie signing secret (REQUIRED, at least 32 bytes)
# Generate with: openssl rand -base64 32
cookieSecret: ""
cookieDuration: 24
# Password hashing pepper (REQUIRED, at least 32 bytes)
# Generate with: openssl rand -base64 32
passwordPepper: ""
passwordIterations: 1000000
# Trust center authentication
trustAuth:
cookieName: "TCT"
cookieDomain: "probo.example.com"
cookieDuration: 24
tokenDuration: 168
reportUrlDuration: 15
ports:
httpChallenge: 80
tlsHttpServer: 443
# Trust token signing secret (REQUIRED, at least 32 bytes)
# Generate with: openssl rand -base64 32
tokenSecret: ""
scope: "trust_center_readonly"
tokenType: "trust_center_access"
# Email configuration
mailer:
senderName: "Probo"
senderEmail: "no-reply@notification.getprobo.com"
smtp:
addr: "sandbox.smtp.mailtrap.io:2525"
user: "2d1b1d0e8b3d0b"
password: "25a8eb11e75e8d"
tlsRequired: true
# smtp:
# addr: "localhost:1025"
# user: ""
# password: ""
# tlsRequired: false
# OpenAI integration (optional)
openai:
apiKey: ""
temperature: 0.1
modelName: "gpt-4o"
# Custom domains configuration (optional)
customDomains:
enabled: false
renewalInterval: 3600
provisionInterval: 30
cnameTarget: "probo.example.com"
acme:
directory: "https://acme-v02.api.letsencrypt.org/directory"
email: "admin@example.com"
keyType: "EC256"
insecureTls: false
# External OAuth2 connectors (optional)
connectors: []
# - name: "github"
# type: "oauth2"
# config:
# client-id: "github-client-id"
# client-secret: "github-client-secret"
# redirect-uri: "https://probo.example.com/api/console/v1/connectors/complete"
# auth-url: "https://github.com/login/oauth/authorize"
# token-url: "https://github.com/login/oauth/access_token"
# PostgreSQL configuration
# For production: Use external managed PostgreSQL (AWS RDS, GCP Cloud SQL, etc.)
# For testing: Enable the included PostgreSQL chart
postgresql:
# -- enabled included Postgres container for demo purposes only using cloudnative-pg
enabled: true
resources:
limits:
memory: "2Gi"
ephemeral-storage: "5Gi"
auth:
postgresUser: probod
postgresPassword: probod
database: probod
# S3 storage configuration
# For production: Use external S3 (AWS S3, GCS, etc.)
# For testing: Enable the included MinIO chart
s3:
# External S3 configuration (used when minio.enabled=false)
region: "us-east-1"
bucket: "probod"
# For AWS S3, leave endpoint empty
# For S3-compatible storage (MinIO, DigitalOcean Spaces, etc.), set the endpoint
endpoint: ""
accessKeyId: "" # REQUIRED when minio.enabled=false: S3 access key
secretAccessKey: "" # REQUIRED when minio.enabled=false: S3 secret key
# MinIO configuration
# Enable included MinIO for testing/development (NOT for production)
minio:
# -- enabled included Minio operator for s3 resource demo purposes
enabled: true
mode: standalone
persistence:
enabled: false
resources:
requests:
memory: 128Mi
primary:
enabled: true
auth:
rootUser: probod
rootPassword: probod
# Headless Chrome for PDF generation
chrome:
# Deploy Chrome in the cluster
enabled: true
replicaCount: 1
image:
repository: chromedp/headless-shell
tag: "140.0.7259.2"
pullPolicy: IfNotPresent
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
# External Chrome configuration
# Used when chrome.enabled=false
external:
addr: ""