Kubernetes
Deploy Probo with Helm and production data services
The Probo Helm chart deploys the Probo application and headless Chrome. For production, connect the chart to an external PostgreSQL database and S3-compatible object storage that your team already operates and backs up.
Use Kubernetes when your team already has established practices for ingress, certificates, secrets, monitoring, and database operations. Kubernetes does not make Probo highly available by itself.
Requirements
Section titled “Requirements”- Kubernetes 1.23 or newer
- Helm 3.8 or newer
kubectlaccess to the target cluster- A PostgreSQL database reachable from the cluster
- An S3 or tested S3-compatible bucket
- An ingress controller and TLS certificate
- An SMTP relay if you want Probo to send email
The database role must be able to create or use the citext, pgcrypto, unaccent, and pg_stat_statements extensions. On PostgreSQL 15 and newer, make the Probo role the owner of the public schema before first startup:
ALTER SCHEMA public OWNER TO probod;GRANT ALL ON SCHEMA public TO probod;Prepare the deployment
Section titled “Prepare the deployment”-
Choose and pin a chart version
The chart is published at
oci://artifact.probo.inc/probo/probo. Set the version you have tested:Terminal window export PROBO_CHART_VERSION="0.0.0"helm show chart oci://artifact.probo.inc/probo/probo \--version "$PROBO_CHART_VERSION"Replace
0.0.0with an available chart version. Do not rely on an unpinned chart in production. -
Generate application secrets
Terminal window umask 077openssl rand -base64 32openssl rand -base64 32openssl rand -base64 32openssl rand -base64 32openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \-out oauth2-signing-key.pemRecord the four generated values separately as the encryption key, cookie secret, password pepper, and trust-token secret.
-
Create non-secret values
Save the following as
values.yamland replace the example hostnames and service details:replicaCount: 2haproxy-ingress:enabled: falseingress:enabled: trueclassName: nginxannotations:cert-manager.io/cluster-issuer: letsencrypt-prodhosts:- host: probo.example.compaths:- path: /pathType: Prefixtls:- secretName: probo-tlshosts:- probo.example.comprobo:baseUrl: probo.example.comcors:allowedOrigins:- https://probo.example.comauth:disableSignup: truecookieDomain: probo.example.comtrustAuth:cookieDomain: probo.example.commailer:senderName: ProbosenderEmail: no-reply@example.comsmtp:addr: smtp.example.com:587tlsRequired: truepostgresql:enabled: falsehost: postgres.example.internalport: 5432database: probodusername: probodseaweedfs:enabled: falses3:region: eu-west-1bucket: probo-productionendpoint: ""usePathStyle: falsechrome:enabled: trueresources:requests:cpu: 500mmemory: 1Gilimits:cpu: 2memory: 4GiThis example assumes an existing ingress controller. If you intentionally want the chart to install HAProxy Ingress, enable
haproxy-ingressand setingress.classNametohaproxy. -
Create secret values
Save the following as
values-secrets.yaml, fill every placeholder, and keep the file outside version control:probo:encryptionKey: "<base64 encryption key>"auth:cookieSecret: "<base64 cookie secret>"passwordPepper: "<base64 password pepper>"trustAuth:tokenSecret: "<base64 trust-token secret>"mailer:smtp:user: "<SMTP username>"password: "<SMTP password>"postgresql:password: "<database password>"s3:accessKeyId: "<S3 access key>"secretAccessKey: "<S3 secret key>"Terminal window chmod 600 values-secrets.yaml oauth2-signing-key.pem -
Render and inspect the manifests
Terminal window helm template probo oci://artifact.probo.inc/probo/probo \--version "$PROBO_CHART_VERSION" \--namespace probo \--values values.yaml \--values values-secrets.yaml \--set-file probo.oauth2.signingKey=oauth2-signing-key.pem \> rendered.yamlInspect the resource names, ingress class, storage, security context, and scheduling behavior.
rendered.yamlcontains secrets; delete it securely after review and do not commit it. -
Install Probo
Terminal window kubectl create namespace probohelm install probo oci://artifact.probo.inc/probo/probo \--version "$PROBO_CHART_VERSION" \--namespace probo \--values values.yaml \--values values-secrets.yaml \--set-file probo.oauth2.signingKey=oauth2-signing-key.pem \--wait \--timeout 10m -
Verify the deployment
Terminal window helm status probo --namespace probokubectl get pods,service,ingress \--namespace probo \--selector app.kubernetes.io/instance=probokubectl rollout status deployment/probo \--namespace probo \--timeout=10mkubectl logs deployment/probo \--namespace probo \--tail=100After DNS and TLS are ready, verify the public endpoint:
Terminal window curl --fail https://probo.example.com/Also test sign-in, file upload, PDF generation, and email delivery before inviting users.
Production decisions
Section titled “Production decisions”Ingress and TLS
Section titled “Ingress and TLS”The chart enables its HAProxy Ingress dependency by default. Disable it when the cluster already has an ingress controller; otherwise the installation can create an unexpected public load balancer.
The chart routes the ingress to Probo’s backoffice port. TLS configuration depends on your ingress controller and certificate system. Confirm that:
- only intended public services receive external addresses;
- HTTP redirects to HTTPS;
- the configured hostname matches
probo.baseUrl; probo.cors.allowedOriginscontains the complete HTTPS origin;- cookies are scoped to the intended domain.
Replicas and local storage
Section titled “Replicas and local storage”Do not claim high availability solely by increasing replicaCount.
The chart mounts /data from emptyDir by default. Enabling persistence creates or mounts one PVC, and the production example uses ReadWriteOnce. Multiple pods scheduled on different nodes may not be able to mount that claim. Test the chart with your storage class and failure model before running multiple replicas.
PostgreSQL and S3 remain the durable system of record. Back up both services to a consistent recovery point and test restoration regularly.
Database connections
Section titled “Database connections”The default pool size is 100 connections per Probo pod. Account for replicas and rolling updates when setting PostgreSQL connection limits. For example, three current pods plus one surge pod can request substantially more than 300 connections.
If your provider requires a custom CA, set postgresql.caBundle or mount a certificate and set postgresql.caBundlePath. Do not disable database certificate verification to work around a CA error.
S3 compatibility
Section titled “S3 compatibility”For AWS S3, leave s3.endpoint empty and s3.usePathStyle false. Other providers may require a custom endpoint and path-style addressing.
Test upload, download, object metadata, and deletion against the exact provider before production use. Azure Blob behind an S3 compatibility proxy has known metadata compatibility limitations and should not be treated as a supported equivalent without testing.
Monitoring
Section titled “Monitoring”Probo exposes metrics on port 8081. If Prometheus Operator is installed, enable the chart’s ServiceMonitor:
metrics: serviceMonitor: enabled: true interval: 30sAt minimum, alert on unavailable pods, restart loops, failed rollouts, database and object-storage errors, certificate expiry, and exhausted database connections. Application logs are written to the container output and should be collected by your cluster logging system.
Upgrade and rollback
Section titled “Upgrade and rollback”Review Probo and chart release notes, back up PostgreSQL and S3, and test the target version in a non-production environment.
export PROBO_CHART_VERSION="0.0.0"
helm upgrade probo oci://artifact.probo.inc/probo/probo \ --version "$PROBO_CHART_VERSION" \ --namespace probo \ --values values.yaml \ --values values-secrets.yaml \ --set-file probo.oauth2.signingKey=oauth2-signing-key.pem \ --wait \ --timeout 10mDatabase migrations run automatically when Probo starts. Watch both the rollout and application logs:
kubectl rollout status deployment/probo --namespace probo --timeout=10mkubectl logs deployment/probo --namespace probo --tail=200If the application release must be rolled back, first determine whether its database migration is backward compatible. Then inspect and select a Helm revision:
helm history probo --namespace probohelm rollback probo REVISION --namespace probo --wait --timeout 10mTroubleshooting
Section titled “Troubleshooting”Pods do not start
Section titled “Pods do not start”kubectl get pods --namespace probokubectl describe pod POD_NAME --namespace probokubectl logs POD_NAME --namespace probo --previouskubectl get events --namespace probo --sort-by=.metadata.creationTimestampCommon causes are invalid secret formats, a missing OAuth signing key, unavailable database extensions, database network policies, and an unreachable S3 endpoint.
The ingress has no address
Section titled “The ingress has no address”kubectl describe ingress probo-http --namespace probokubectl get ingressclassConfirm that ingress.className names an installed controller and inspect that controller’s logs. If the chart unexpectedly installed HAProxy, review the haproxy-ingress.enabled value.
A rollout cannot mount /data
Section titled “A rollout cannot mount /data”kubectl get pvc --namespace probokubectl describe pvc probo --namespace proboCheck the volume’s access mode, storage class, availability zone, and pod scheduling events. A single ReadWriteOnce volume is not a portable shared-storage design for replicas on multiple nodes.
See the chart’s values.yaml and the environment variable reference for additional configuration.