The Helm chart in charts/itsaplan/ deploys the full stack on any Kubernetes 1.24+ cluster. It runs the published
images from GHCR, with built-in PostgreSQL and MinIO that can each be swapped for an external service.
- Kubernetes 1.24+
- Helm 3.10+
Create a values file for the deployment:
api:
env:
API_URL: "https://api.example.com"
APP_URL: "https://app.example.com"
S3_BUCKET: "planner-attachments"
secrets:
postgresPassword: "" # openssl rand -base64 32
betterAuthSecret: "" # openssl rand -base64 32
appEncryptionKey: "" # openssl rand -base64 32
s3AccessKeyId: "minioadmin"
s3SecretAccessKey: "minioadmin"Install the chart:
helm install itsaplan charts/itsaplan -f values.yamlThe API applies database migrations on startup. The first account registered becomes the instance admin.
| Resource | Kind | Condition |
|---|---|---|
| API (Elysia) | Deployment + Service | always |
| Web (Next.js) | Deployment + Service | always |
| Worker | Deployment | always |
| Bot (Telegram) | Deployment | bot.enabled (default true) |
| PostgreSQL | StatefulSet + Service + PVC | postgresql.enabled (default true) |
| MinIO | Deployment + Service + PVC | minio.enabled (default true) |
| MinIO bucket init | Job (Helm hook) | minio.enabled |
Ingress, TLS certificates, and a ServiceAccount are available but disabled by default.
The API cannot serve behind a path prefix (better-auth mounts at /api/auth/* and treats a path inside API_URL as a
replacement for its base path). The chart offers two modes instead.
The web and API each get their own hostname. Works with any ingress controller.
ingress:
enabled: true
className: nginx
host: app.example.com
tls:
enabled: true
secretName: app-tls
api:
mode: separate-host
host: api.example.com
tls:
enabled: true
secretName: api-tlsThe API shares the web hostname but is served on a dedicated Traefik entrypoint (a separate port). Requires Traefik and
the IngressRoute CRD.
ingress:
enabled: true
className: traefik
host: app.example.com
tls:
enabled: true
secretName: app-tls
api:
mode: traefik-entrypoint
entryPoint: apisecure
publicPort: 8443
tls:
enabled: true
secretName: app-tlscertificate:
enabled: true
issuerName: letsencrypt-prod
issuerKind: ClusterIssuer
dnsNames:
- app.example.com
- api.example.comThe generated secret is named <release>-itsaplan-tls. Reference it in
ingress.tls.secretName and ingress.api.tls.secretName.
Disable the built-in PostgreSQL and provide a connection string:
postgresql:
enabled: false
externalDatabase:
url: "postgres://user:password@db.example.com:5432/itsaplan"Disable the built-in MinIO and point to an external S3-compatible store:
minio:
enabled: false
externalS3:
endpoint: "https://s3.us-east-1.amazonaws.com"
api:
env:
S3_BUCKET: "my-bucket"
S3_REGION: "us-east-1"
S3_FORCE_PATH_STYLE: "false"
secrets:
s3AccessKeyId: "AKIA..."
s3SecretAccessKey: "..."S3_FORCE_PATH_STYLE is false for AWS and Cloudflare R2, true for MinIO.
The chart creates a Kubernetes Secret with plaintext stringData. For production, store secrets externally (Sealed
Secrets, External Secrets Operator, SOPS, etc.) and inject them via a values override or a secret store CSI driver.
helm upgrade itsaplan charts/itsaplan -f values.yamlConfig and secret changes trigger a rolling restart automatically (the deployments carry a checksum annotation on the ConfigMap and Secret).
bot:
enabled: falseThe full list of values with defaults is in charts/itsaplan/README.md.
For Docker Compose, see self-hosting.md. For a managed deploy, see coolify.md or railway.md.