This guide shows you how to deploy Sure on Kubernetes using the official Helm chart. The chart supports web (Rails) and worker (Sidekiq) workloads, optional in-cluster PostgreSQL and Redis, and production-grade features like pre-upgrade migrations, pod security contexts, and horizontal pod autoscaling.
Prerequisites
- Kubernetes >= 1.25
- Helm >= 3.10
- Basic familiarity with Kubernetes and Helm
Features
- Web (Rails) deployment with service and optional ingress
- Worker (Sidekiq) deployment
- Optional database migrations via Helm hook job or initContainer
- Optional subcharts for PostgreSQL (CloudNativePG) and Redis (OT-CONTAINER-KIT redis-operator)
- Security best practices: runAsNonRoot, readOnlyRootFilesystem, no hardcoded secrets
- Scalability: replicas, resources, topology spread constraints, optional HPAs
- Optional CronJobs for custom tasks
Installation
Add Helm repositories
Add the Sure Helm repository:
helm repo add sure https://we-promise.github.io/sure
helm repo update
If you plan to use the bundled PostgreSQL or Redis subcharts, add their repositories as well:
helm repo add cloudnative-pg https://cloudnative-pg.github.io/charts
helm repo add ot-helm https://ot-container-kit.github.io/helm-charts
helm repo update
Quickstart (turnkey self-hosting)
This installs CloudNativePG operator with a Postgres cluster and Redis managed by the OT redis-operator.
For production stability, use immutable image tags (for example, image.tag=v1.2.3) instead of latest.
Do not pass production secrets with --set on the command line. Use a values file with restricted permissions or a Kubernetes Secret as described in Secrets management.
# Create namespace
kubectl create ns sure || true
# Create a values file for the initial secret
SECRET_KEY_BASE=$(openssl rand -hex 32)
cat > values-quickstart.yaml <<EOF
image:
tag: v1.2.3
rails:
secret:
enabled: true
values:
SECRET_KEY_BASE: "$SECRET_KEY_BASE"
EOF
chmod 600 values-quickstart.yaml
# Install chart with a pinned image tag
helm upgrade --install sure sure/sure \
-n sure \
-f values-quickstart.yaml
Expose the app via an ingress (see configuration below) or port-forward:
kubectl port-forward svc/sure 8080:80 -n sure
Navigate to http://localhost:8080 to access Sure.
Configuration
Using external Postgres and Redis
To use external managed databases instead of the bundled subcharts, store connection strings in a Kubernetes Secret and reference them from your values file. See Secrets management for the general pattern.
cnpg:
enabled: false
redisOperator:
managed:
enabled: false
redisSimple:
enabled: false
rails:
extraEnv:
DATABASE_URL:
valueFrom:
secretKeyRef:
name: external-db-credentials
key: database-url
REDIS_URL:
valueFrom:
secretKeyRef:
name: external-db-credentials
key: redis-url
Deployment profiles
Simple single-node
Minimal setup for development or small deployments:
encryptionEnv.enabled: true tells the chart to wire the Active Record encryption keys from your secret into the app container.
SELF_HOSTED: "true" enables self-hosted behavior inside Sure.
- Replace
longhorn with a storage class that exists in your cluster.
image:
repository: ghcr.io/we-promise/sure
tag: "v1.0.0"
pullPolicy: IfNotPresent
rails:
existingSecret: sure-secrets
encryptionEnv:
enabled: true
settings:
SELF_HOSTED: "true"
cnpg:
enabled: true
cluster:
enabled: true
name: sure-db
instances: 1
storage:
size: 8Gi
storageClassName: longhorn
redisOperator:
enabled: true
managed:
enabled: true
mode: standalone
replicas: 1
persistence:
enabled: true
className: longhorn
size: 8Gi
migrations:
strategy: job
HA k3s profile
High availability setup with multiple replicas and synchronous replication:
cnpg:
enabled: true
cluster:
enabled: true
name: sure-db
instances: 3
storage:
size: 20Gi
storageClassName: longhorn
minSyncReplicas: 1
maxSyncReplicas: 2
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
cnpg.io/cluster: sure-db
redisOperator:
enabled: true
managed:
enabled: true
mode: replication
replicas: 3
persistence:
enabled: true
className: longhorn
size: 8Gi
migrations:
strategy: job
initContainer:
enabled: true
hpa:
web:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
Secrets management
Generate strong secrets before creating the Kubernetes Secret:
openssl rand -hex 32 # SECRET_KEY_BASE
openssl rand -hex 32 # ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
openssl rand -hex 32 # ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY
openssl rand -hex 32 # redis-password
openssl rand -hex 64 # ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
Create a Kubernetes secret with the required credentials:
apiVersion: v1
kind: Secret
metadata:
name: sure-secrets
type: Opaque
stringData:
# Rails secrets
SECRET_KEY_BASE: "__SET_SECRET__"
# Active Record Encryption keys (required for self-hosted mode)
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY: "__SET_SECRET__"
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY: "__SET_SECRET__"
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT: "__SET_SECRET__"
# Redis password
redis-password: "__SET_SECRET__"
# Optional external service URLs
database-url: "postgresql://user:pass@db.example.com:5432/sure"
redis-url: "redis://:pass@redis.example.com:6379/0"
Apply the secret:
kubectl apply -f sure-secrets.yaml -n sure
Reference the secret in your values:
rails:
existingSecret: sure-secrets
redisOperator:
managed:
enabled: true
auth:
existingSecret: sure-secrets
passwordKey: redis-password
Ingress configuration
Enable ingress to expose Sure externally:
ingress:
enabled: true
className: "nginx"
hosts:
- host: finance.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts: [finance.example.com]
secretName: finance-tls
Horizontal pod autoscaling
Enable HPAs for automatic scaling based on CPU utilization:
hpa:
web:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
worker:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
Updating
To update to a new version of Sure:
# Update the Helm repository
helm repo update sure
# Update the deployment with a new image tag
helm upgrade sure sure/sure -n sure \
--set image.tag=v1.2.4 \
-f your-values.yaml
The chart will automatically run database migrations before deploying the new version.
Backup and restore
PostgreSQL backups with CloudNativePG
CloudNativePG supports volume snapshot backups:
cnpg:
cluster:
backup:
method: volumeSnapshot
volumeSnapshot:
className: longhorn
Manual backup
Create a manual backup of your PostgreSQL database:
# Get the primary pod name
PRIMARY_POD=$(kubectl get pod -n sure -l cnpg.io/cluster=sure-db,role=primary -o name)
# Create a backup
kubectl exec -n sure $PRIMARY_POD -- pg_dump -U sure sure_production > backup.sql
[!NOTE]
If you customized the PostgreSQL username or database name in your chart values, replace sure and sure_production in the backup and restore commands below.
Restore from backup
# Copy backup to pod
kubectl cp backup.sql sure/$PRIMARY_POD:/tmp/backup.sql
# Restore
kubectl exec -n sure $PRIMARY_POD -- psql -U sure sure_production -f /tmp/backup.sql
Troubleshooting
View logs
# Web logs
kubectl logs -n sure -l app.kubernetes.io/component=web
# Worker logs
kubectl logs -n sure -l app.kubernetes.io/component=worker
# Migration job logs
kubectl logs -n sure -l job-name=sure-migrate
Check pod status
Verify database connectivity
# Test connection from web pod
kubectl exec -n sure deploy/sure-web -- rails runner "puts ActiveRecord::Base.connection.execute('SELECT 1').first"
Run Helm tests
After installation, verify the deployment:
Uninstall
To remove Sure from your cluster:
helm uninstall sure -n sure
This will not delete PersistentVolumeClaims. Before deleting PVCs, make sure the namespace does not contain volumes for anything other than Sure.
kubectl get pvc -n sure
kubectl delete pvc -n sure <sure-pvc-name>
Getting help
If you find bugs or have feature requests: