Compare commits

..

7 Commits

22 changed files with 2217 additions and 2310 deletions

View File

@ -2,7 +2,7 @@ apiVersion: v2
name: donetick
description: Donetick helm chart for Kubernetes
type: application
version: 1.0.4
version: 1.0.6
appVersion: "v0.1.60"
maintainers:
- name: Richard Tomik

View File

@ -1,14 +1,12 @@
# Donetick Helm Chart
A Helm chart for deploying the Donetick task management application on Kubernetes.
A Helm chart for deploying [Donetick](https://github.com/donetick/donetick) on Kubernetes.
## Introduction
This chart deploys [Donetick](https://github.com/donetick/donetick) on a Kubernetes cluster using the Helm package manager.
Source code can be found here:
- https://github.com/rtomik/helm-charts/tree/main/charts/donetick
This chart deploys Donetick, a task management application, on a Kubernetes cluster using the Helm package manager. Donetick supports SQLite or PostgreSQL databases, real-time updates via WebSockets, OAuth2 authentication, and push notifications via Telegram and Pushover.
Source code: https://github.com/rtomik/helm-charts/tree/main/charts/donetick
## Prerequisites
@ -18,98 +16,72 @@ Source code can be found here:
## Installing the Chart
To install the chart with the release name `donetick`:
```bash
$ helm repo add donetick-chart https://rtomik.github.io/helm-charts
$ helm install donetick donetick-chart/donetick
helm repo add rtomik https://rtomik.github.io/helm-charts
helm install donetick rtomik/donetick
```
> **Tip**: List all releases using `helm list`
## Uninstalling the Chart
```bash
helm uninstall donetick
```
## Configuration Examples
### Basic Installation with SQLite (Default)
### Minimal Installation (SQLite)
The chart works out of the box with SQLite — no additional configuration required:
```bash
helm install donetick donetick-chart/donetick
helm install donetick rtomik/donetick
```
### Installation with External PostgreSQL
Create a values file for PostgreSQL configuration:
### PostgreSQL Configuration
```yaml
config:
database:
type: "postgres"
host: "postgresql.database.svc.cluster.local"
port: 5432
name: "donetick"
secrets:
existingSecret: "donetick-postgres-secret"
userKey: "username"
passwordKey: "password"
jwt:
secret: "your-secure-jwt-secret-at-least-32-characters-long"
server:
cors_allow_origins:
- "https://your-domain.com"
features:
notifications: true
realtime: true
ingress:
enabled: true
hosts:
- host: donetick.your-domain.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- donetick.your-domain.com
persistence:
enabled: true
size: "5Gi"
```
### Production with Existing Secrets
```yaml
# values-postgres.yaml
config:
database:
type: "postgres"
host: "postgresql.database.svc.cluster.local"
port: 5432
user: "donetick"
password: "your-secure-password"
name: "donetick"
migration: true
# Update JWT secret for production
jwt:
secret: "your-secure-jwt-secret-at-least-32-characters-long"
# Configure server settings
server:
cors_allow_origins:
- "https://your-domain.com"
- "http://localhost:5173"
# Enable features as needed
features:
notifications: true
realtime: true
oauth: false
# Enable ingress for external access
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
hosts:
- host: donetick.your-domain.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: donetick-tls
hosts:
- donetick.your-domain.com
# Configure persistence
persistence:
enabled: true
storageClass: "fast-ssd"
size: "5Gi"
```
Install with PostgreSQL configuration:
```bash
helm install donetick donetick-chart/donetick -f values-postgres.yaml
```
### Production Installation with External Secrets
For production deployments, use Kubernetes secrets for sensitive data:
```yaml
# values-production.yaml
config:
database:
type: "postgres"
host: "postgresql.database.svc.cluster.local"
port: 5432
name: "donetick"
# Use existing secret for postgres credentials
database:
type: "postgres"
host: "postgresql.database.svc.cluster.local"
@ -120,14 +92,11 @@ config:
userKey: "username"
passwordKey: "password"
# Use existing secret for JWT
jwt:
existingSecret: "donetick-jwt-secret"
secretKey: "jwt-secret"
secretKey: "jwtSecret"
session_time: "168h"
max_refresh: "168h"
# OAuth2 configuration with secrets
oauth2:
existingSecret: "donetick-oauth-secret"
clientIdKey: "client-id"
@ -137,26 +106,17 @@ config:
user_info_url: "https://your-oauth-provider.com/userinfo"
redirect_url: "https://donetick.your-domain.com/auth/callback"
# Production server settings
server:
cors_allow_origins:
- "https://donetick.your-domain.com"
rate_limit: 100
rate_period: "60s"
# Enable production features
features:
notifications: true
realtime: true
oauth: true
# Security context for production
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
# Resource limits for production
resources:
limits:
cpu: 500m
@ -165,299 +125,244 @@ resources:
cpu: 100m
memory: 128Mi
# Ingress with TLS
ingress:
enabled: true
className: "nginx"
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
hosts:
- host: donetick.your-domain.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: donetick-tls
hosts:
- hosts:
- donetick.your-domain.com
```
Create the required secrets:
```bash
# Postgres secret
kubectl create secret generic donetick-postgres-secret \
--from-literal=username='donetick' \
--from-literal=password='your-secure-db-password'
# JWT secret
kubectl create secret generic donetick-jwt-secret \
--from-literal=jwt-secret='your-very-secure-jwt-secret-at-least-32-characters-long'
--from-literal=jwtSecret='your-very-secure-jwt-secret-at-least-32-characters-long'
# OAuth secret (if using OAuth)
kubectl create secret generic donetick-oauth-secret \
--from-literal=client-id='your-oauth-client-id' \
--from-literal=client-secret='your-oauth-client-secret'
```
Install with production configuration:
```bash
helm install donetick donetick-chart/donetick -f values-production.yaml
```
## Uninstalling the Chart
To uninstall/delete the `donetick` deployment:
```bash
helm uninstall donetick
```
## Parameters
### Global parameters
### Global Parameters
| Name | Description | Value |
|------------------------|-------------------------------------------------------------------------------------|-------|
| `nameOverride` | String to partially override the release name | `""` |
| `fullnameOverride` | String to fully override the release name | `""` |
| Name | Description | Default |
|------|-------------|---------|
| `nameOverride` | Override the release name | `""` |
| `fullnameOverride` | Fully override the release name | `""` |
### Image parameters
### Image Parameters
| Name | Description | Value |
|-------------------------|--------------------------------------------------------------------------------------|--------------------|
| Name | Description | Default |
|------|-------------|---------|
| `image.repository` | Donetick image repository | `donetick/donetick` |
| `image.tag` | Donetick image tag | `v0.1.60` |
| `image.pullPolicy` | Donetick image pull policy | `IfNotPresent` |
| `imagePullSecrets` | Global Docker registry secret names as an array | `[]` |
| `image.tag` | Image tag | `v0.1.60` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `imagePullSecrets` | Image pull secrets | `[]` |
### Secret Management
### Deployment Parameters
| Name | Description | Value |
|----------------------------------------|--------------------------------------------------------------------|---------------------|
| `config.jwt.existingSecret` | Name of existing secret for JWT token | `""` |
| `config.jwt.secretKey` | Key in the existing secret for JWT token | `"jwtSecret"` |
| `config.oauth2.existingSecret` | Name of existing secret for OAuth2 credentials | `""` |
| `config.oauth2.clientIdKey` | Key in the existing secret for OAuth2 client ID | `"client-id"` |
| `config.oauth2.clientSecretKey` | Key in the existing secret for OAuth2 client secret | `"client-secret"` |
| `config.database.secrets.existingSecret` | Name of existing secret for postgres credentials | `""` |
| `config.database.secrets.userKey` | Key in the existing secret for postgres username | `"username"` |
| `config.database.secrets.passwordKey` | Key in the existing secret for postgres password | `"password"` |
| Name | Description | Default |
|------|-------------|---------|
| `replicaCount` | Number of replicas | `1` |
| `revisionHistoryLimit` | Revisions to retain | `3` |
| `startupArgs` | Optional startup arguments | `[]` |
| `podSecurityContext.runAsNonRoot` | Run as non-root | `true` |
| `podSecurityContext.runAsUser` | User ID | `1000` |
| `podSecurityContext.fsGroup` | Filesystem group ID | `1000` |
| `nodeSelector` | Node selector | `{}` |
| `tolerations` | Tolerations | `[]` |
| `affinity` | Affinity rules | `{}` |
| `podAnnotations` | Pod annotations | `{}` |
### Deployment parameters
### Service Parameters
| Name | Description | Value |
|--------------------------------------|--------------------------------------------------------------------------|-----------|
| `replicaCount` | Number of Donetick replicas | `1` |
| `revisionHistoryLimit` | Number of revisions to retain for rollback | `3` |
| `podSecurityContext.runAsNonRoot` | Run containers as non-root user | `true` |
| `podSecurityContext.runAsUser` | User ID for the container | `1000` |
| `podSecurityContext.fsGroup` | Group ID for the container filesystem | `1000` |
| `containerSecurityContext` | Security context for the container | See values.yaml |
| `nodeSelector` | Node labels for pod assignment | `{}` |
| `tolerations` | Tolerations for pod assignment | `[]` |
| `affinity` | Affinity for pod assignment | `{}` |
| Name | Description | Default |
|------|-------------|---------|
| `service.type` | Service type | `ClusterIP` |
| `service.port` | Service port | `2021` |
| `service.annotations` | Service annotations | `{}` |
### Service parameters
### Ingress Parameters
| Name | Description | Value |
|----------------------------|------------------------------------------------------|-------------|
| `service.type` | Kubernetes Service type | `ClusterIP` |
| `service.port` | Service HTTP port | `2021` |
| `service.annotations` | Additional annotations for Service | `{}` |
### Pod Configuration
| Name | Description | Value |
|----------------------------|------------------------------------------------------|-------------|
| `podAnnotations` | Additional annotations for pods | `{}` |
### Ingress parameters
| Name | Description | Value |
|----------------------------|------------------------------------------------------|----------------------|
| `ingress.enabled` | Enable ingress record generation | `true` |
| `ingress.className` | IngressClass name | `"traefik"` |
| `ingress.annotations` | Additional annotations for the Ingress resource | See values.yaml |
| `ingress.hosts` | Array of host and path objects | See values.yaml |
| `ingress.tlsSecretName` | Global TLS secret name for all hosts | `""` |
| Name | Description | Default |
|------|-------------|---------|
| `ingress.enabled` | Enable ingress | `false` |
| `ingress.className` | Ingress class name | `""` |
| `ingress.annotations` | Ingress annotations | See values.yaml |
| `ingress.hosts` | Ingress hosts | See values.yaml |
| `ingress.tls` | TLS configuration | See values.yaml |
| `ingress.tls[].secretName` | Host-specific TLS secret name (overrides global) | `""` |
### Persistence parameters
### Persistence Parameters
| Name | Description | Value |
|-------------------------------|------------------------------------------------------|---------------|
| `persistence.enabled` | Enable persistence using PVC | `false` |
| `persistence.storageClass` | PVC Storage Class | `""` |
| `persistence.accessMode` | PVC Access Mode | `ReadWriteOnce` |
| `persistence.size` | PVC Size | `1Gi` |
### Health Checks
| Name | Description | Value |
|----------------------------------------|------------------------------------------------------|---------------|
| `probes.startup.enabled` | Enable startup probe | `true` |
| `probes.startup.initialDelaySeconds` | Initial delay for startup probe | `10` |
| `probes.startup.periodSeconds` | Period for startup probe | `10` |
| `probes.startup.failureThreshold` | Failure threshold for startup probe | `30` |
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.initialDelaySeconds` | Initial delay for liveness probe | `30` |
| `probes.liveness.periodSeconds` | Period for liveness probe | `10` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.initialDelaySeconds` | Initial delay for readiness probe | `5` |
| `probes.readiness.periodSeconds` | Period for readiness probe | `5` |
### Application Configuration
| Name | Description | Value |
|----------------------------------------|------------------------------------------------------|---------------|
| `config.name` | Application name | `selfhosted` |
| `config.is_done_tick_dot_com` | Enable donetick.com features | `false` |
| `config.is_user_creation_disabled` | Disable user registration | `false` |
### Real-time Configuration
| Name | Description | Value |
|----------------------------------------|------------------------------------------------------|---------------|
| `config.realtime.max_connections` | Maximum WebSocket connections | `100` |
| `config.realtime.ping_interval` | WebSocket ping interval | `30s` |
| `config.realtime.pong_wait` | WebSocket pong wait timeout | `60s` |
| `config.realtime.write_wait` | WebSocket write timeout | `10s` |
| `config.realtime.max_message_size` | Maximum WebSocket message size | `512` |
| Name | Description | Default |
|------|-------------|---------|
| `persistence.enabled` | Enable persistence | `false` |
| `persistence.storageClass` | Storage class | `""` |
| `persistence.accessMode` | Access mode | `ReadWriteOnce` |
| `persistence.size` | PVC size | `1Gi` |
| `persistence.annotations` | PVC annotations | `{}` |
### Database Configuration
| Name | Description | Value |
|----------------------------------------|------------------------------------------------------|---------------|
| `config.database.type` | Database type (sqlite or postgres) | `sqlite` |
| `config.database.migration` | Enable database migrations | `true` |
| `config.database.host` | PostgreSQL host (when type=postgres) | `""` |
| `config.database.port` | PostgreSQL port (when type=postgres) | `5432` |
| `config.database.user` | PostgreSQL user (when type=postgres) | `""` |
| `config.database.password` | PostgreSQL password (when type=postgres) | `""` |
| `config.database.name` | PostgreSQL database name (when type=postgres) | `""` |
| Name | Description | Default |
|------|-------------|---------|
| `config.database.type` | Database type (`sqlite` or `postgres`) | `sqlite` |
| `config.database.migration` | Enable migrations | `true` |
| `config.database.migration_skip` | Skip migrations | `false` |
| `config.database.migration_retry` | Migration retry count | `3` |
| `config.database.migration_timeout` | Migration timeout | `600s` |
| `config.database.host` | PostgreSQL host | `""` |
| `config.database.port` | PostgreSQL port | `5432` |
| `config.database.name` | PostgreSQL database name | `""` |
| `config.database.secrets.existingSecret` | Existing secret for credentials | `""` |
| `config.database.secrets.userKey` | Key for username in secret | `username` |
| `config.database.secrets.passwordKey` | Key for password in secret | `password` |
### JWT Configuration
| Name | Description | Value |
|----------------------------------------|------------------------------------------------------|---------------|
| `config.jwt.secret` | JWT signing secret | `changeme-this-secret-should-be-at-least-32-characters-long` |
| `config.jwt.session_time` | JWT session duration | `168h` |
| `config.jwt.max_refresh` | JWT maximum refresh duration | `168h` |
| Name | Description | Default |
|------|-------------|---------|
| `config.jwt.secret` | JWT signing secret (min 32 chars) | `changeme-...` |
| `config.jwt.session_time` | Session duration | `168h` |
| `config.jwt.max_refresh` | Max refresh duration | `168h` |
| `config.jwt.existingSecret` | Existing secret for JWT | `""` |
| `config.jwt.secretKey` | Key in secret | `jwtSecret` |
### Server Configuration
| Name | Description | Value |
|----------------------------------------|------------------------------------------------------|---------------|
| Name | Description | Default |
|------|-------------|---------|
| `config.server.port` | Server port | `2021` |
| `config.server.read_timeout` | Server read timeout | `10s` |
| `config.server.write_timeout` | Server write timeout | `10s` |
| `config.server.read_timeout` | Read timeout | `10s` |
| `config.server.write_timeout` | Write timeout | `10s` |
| `config.server.rate_period` | Rate limiting period | `60s` |
| `config.server.rate_limit` | Rate limiting requests per period | `300` |
| `config.server.rate_limit` | Rate limit per period | `300` |
| `config.server.serve_frontend` | Serve frontend files | `true` |
| `config.server.cors_allow_origins` | CORS allowed origins | See values.yaml |
### OAuth2 Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.oauth2.client_id` | OAuth2 client ID | `""` |
| `config.oauth2.client_secret` | OAuth2 client secret | `""` |
| `config.oauth2.existingSecret` | Existing secret for credentials | `""` |
| `config.oauth2.clientIdKey` | Key for client ID in secret | `client-id` |
| `config.oauth2.clientSecretKey` | Key for client secret in secret | `client-secret` |
| `config.oauth2.auth_url` | Authorization URL | `""` |
| `config.oauth2.token_url` | Token URL | `""` |
| `config.oauth2.user_info_url` | User info URL | `""` |
| `config.oauth2.redirect_url` | Redirect URL | `""` |
### Real-time Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.realtime.max_connections` | Max WebSocket connections | `100` |
| `config.realtime.ping_interval` | Ping interval | `30s` |
| `config.realtime.pong_wait` | Pong wait timeout | `60s` |
| `config.realtime.write_wait` | Write timeout | `10s` |
| `config.realtime.max_message_size` | Max message size | `512` |
### Notification Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.telegram.token` | Telegram bot token | `""` |
| `config.pushover.token` | Pushover token | `""` |
### Feature Flags
| Name | Description | Value |
|----------------------------------------|------------------------------------------------------|---------------|
| Name | Description | Default |
|------|-------------|---------|
| `config.features.notifications` | Enable notifications | `true` |
| `config.features.realtime` | Enable real-time features | `true` |
| `config.features.oauth` | Enable OAuth authentication | `false` |
| `config.features.oauth` | Enable OAuth | `false` |
| `config.is_user_creation_disabled` | Disable user registration | `false` |
## Database Setup
### Resource Parameters
### PostgreSQL Requirements
| Name | Description | Default |
|------|-------------|---------|
| `resources` | Resource limits and requests | `{}` |
When using PostgreSQL, ensure you have:
### Health Check Parameters
1. **Database Created**: Create a database for Donetick
```sql
CREATE DATABASE donetick;
CREATE USER donetick WITH PASSWORD 'your-secure-password';
GRANT ALL PRIVILEGES ON DATABASE donetick TO donetick;
```
| Name | Description | Default |
|------|-------------|---------|
| `probes.startup.enabled` | Enable startup probe | `true` |
| `probes.startup.path` | Startup probe path | `/health` |
| `probes.startup.initialDelaySeconds` | Startup initial delay | `30` |
| `probes.startup.periodSeconds` | Startup period | `15` |
| `probes.startup.failureThreshold` | Startup failure threshold | `80` |
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.path` | Liveness probe path | `/health` |
| `probes.liveness.initialDelaySeconds` | Liveness initial delay | `30` |
| `probes.liveness.periodSeconds` | Liveness period | `10` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.path` | Readiness probe path | `/health` |
| `probes.readiness.initialDelaySeconds` | Readiness initial delay | `5` |
| `probes.readiness.periodSeconds` | Readiness period | `5` |
2. **Network Access**: Ensure Donetick can reach your PostgreSQL instance
3. **Proper Credentials**: Configure database credentials in values or secrets
### Autoscaling Parameters
### Database Migration
Donetick automatically runs database migrations on startup when `config.database.migration: true`. For production:
1. **Review Migrations**: Check what migrations will be applied
2. **Backup Database**: Always backup before running migrations
3. **Monitor Startup**: Watch pod logs during initial deployment
| Name | Description | Default |
|------|-------------|---------|
| `autoscaling.enabled` | Enable HPA | `false` |
| `autoscaling.minReplicas` | Min replicas | `1` |
| `autoscaling.maxReplicas` | Max replicas | `5` |
| `autoscaling.targetCPUUtilizationPercentage` | Target CPU | `80` |
| `autoscaling.targetMemoryUtilizationPercentage` | Target memory | `80` |
## Troubleshooting
### Common Issues
### Real-time Configuration Panic
#### 1. Real-time Configuration Panic
**Error**: `Invalid real-time configuration: maxConnections must be positive, got 0`
**Solution**: Ensure the real-time configuration is properly set:
```yaml
config:
realtime:
max_connections: 100 # Must be > 0
```
Ensure `config.realtime.max_connections` is set to a positive value (default: `100`).
#### 2. Database Connection Issues
**Error**: Database connection failures
### Database Connection Issues
**Solutions**:
- Verify PostgreSQL is running and accessible
- Check database credentials in secrets
- Ensure database name exists
- Verify network policies allow connection
- Ensure the database exists
- Verify network policies allow the connection
#### 3. JWT Secret Issues
**Error**: JWT authentication failures
### JWT Authentication Failures
**Solution**: Ensure JWT secret is at least 32 characters:
```yaml
config:
jwt:
secret: "your-very-secure-jwt-secret-at-least-32-characters-long"
```
Ensure `config.jwt.secret` is at least 32 characters long. For production, use `config.jwt.existingSecret`.
#### 4. CORS Issues
**Error**: Cross-origin request blocked
### CORS Issues
Add your domain to `config.server.cors_allow_origins`:
**Solution**: Configure CORS origins:
```yaml
config:
server:
cors_allow_origins:
- "https://your-domain.com"
- "http://localhost:5173"
```
### Debugging
Check application logs:
```bash
kubectl logs deployment/donetick -f
```
Check configuration:
```bash
kubectl get configmap donetick-configmap -o yaml
```
Verify secrets:
```bash
kubectl get secret donetick-secrets -o yaml
```
## Links
## Contributing
Please feel free to contribute by opening issues or pull requests at:
https://github.com/rtomik/helm-charts
## License
This Helm chart is licensed under the MIT License.
- [Donetick GitHub](https://github.com/donetick/donetick)
- [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/donetick)

View File

@ -4,10 +4,9 @@ A Helm chart for deploying [Jellyseerr](https://github.com/fallenbagel/jellyseer
## Introduction
This chart deploys Jellyseerr on a Kubernetes cluster using the Helm package manager. Jellyseerr is a fork of Overseerr for Jellyfin support.
This chart deploys Jellyseerr, a media request management application for Jellyfin, on a Kubernetes cluster using the Helm package manager. Jellyseerr is a fork of Overseerr with native Jellyfin support.
Source code can be found here:
- https://github.com/rtomik/helm-charts/tree/main/charts/jellyseerr
Source code: https://github.com/rtomik/helm-charts/tree/main/charts/jellyseerr
## Prerequisites
@ -17,104 +16,35 @@ Source code can be found here:
## Installing the Chart
To install the chart with the release name `jellyseerr`:
```bash
helm repo add rtomik-charts https://rtomik.github.io/helm-charts
helm install jellyseerr rtomik-charts/jellyseerr
helm repo add rtomik https://rtomik.github.io/helm-charts
helm install jellyseerr rtomik/jellyseerr
```
> **Tip**: List all releases using `helm list`
## Uninstalling the Chart
To uninstall/delete the `jellyseerr` deployment:
```bash
helm uninstall jellyseerr
```
## Parameters
## Configuration Examples
### Global parameters
### Minimal Installation
| Name | Description | Value |
|------------------------|---------------------------------------------------------------|--------|
| `nameOverride` | String to partially override the release name | `""` |
| `fullnameOverride` | String to fully override the release name | `""` |
```yaml
ingress:
enabled: true
hosts:
- host: jellyseerr.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- jellyseerr.example.com
```
### Image parameters
| Name | Description | Value |
|-------------------------|--------------------------------------------------------------|--------------------------------|
| `image.repository` | Jellyseerr image repository | `ghcr.io/fallenbagel/jellyseerr` |
| `image.tag` | Jellyseerr image tag | `latest` |
| `image.pullPolicy` | Jellyseerr image pull policy | `IfNotPresent` |
| `imagePullSecrets` | Global Docker registry secret names as an array | `[]` |
### Deployment parameters
| Name | Description | Value |
|--------------------------------------|--------------------------------------------------|-----------|
| `replicaCount` | Number of Jellyseerr replicas | `1` |
| `revisionHistoryLimit` | Number of revisions to retain for rollback | `3` |
| `podSecurityContext.runAsNonRoot` | Run containers as non-root user | `true` |
| `podSecurityContext.runAsUser` | User ID for the container | `1000` |
| `podSecurityContext.fsGroup` | Group ID for the container filesystem | `1000` |
| `containerSecurityContext` | Security context for the container | See values.yaml |
| `nodeSelector` | Node labels for pod assignment | `{}` |
| `tolerations` | Tolerations for pod assignment | `[]` |
| `affinity` | Affinity for pod assignment | `{}` |
### Service parameters
| Name | Description | Value |
|----------------------------|----------------------------------------------|-------------|
| `service.type` | Kubernetes Service type | `ClusterIP` |
| `service.port` | Service HTTP port | `5055` |
### Ingress parameters
| Name | Description | Value |
|----------------------------|----------------------------------------------|------------------------|
| `ingress.enabled` | Enable ingress record generation | `false` |
| `ingress.className` | IngressClass name | `""` |
| `ingress.annotations` | Additional annotations for the Ingress resource | `{}` |
| `ingress.hosts` | Array of host and path objects | See values.yaml |
| `ingress.tls` | TLS configuration | `[]` |
### Persistence parameters
| Name | Description | Value |
|-------------------------------|----------------------------------------------|-----------------|
| `persistence.enabled` | Enable persistence using PVC | `true` |
| `persistence.existingClaim` | Use an existing PVC | `""` |
| `persistence.storageClass` | PVC Storage Class | `""` |
| `persistence.accessMode` | PVC Access Mode | `ReadWriteOnce` |
| `persistence.size` | PVC Storage Size | `1Gi` |
| `persistence.annotations` | Additional custom annotations for the PVC | `{}` |
### Environment variables
| Name | Description | Value |
|--------------------------|----------------------------------------------|-----------------|
| `env` | Environment variables for Jellyseerr | See values.yaml |
| `extraEnv` | Additional environment variables | `[]` |
### Resources parameters
| Name | Description | Value |
|--------------------------|----------------------------------------------|-----------------|
| `resources.limits` | The resources limits for containers | See values.yaml |
| `resources.requests` | The resources requests for containers | See values.yaml |
## Configuration
The following table lists the configurable parameters of the Jellyseerr chart and their default values.
### Environment Variables
You can configure Jellyseerr by setting environment variables:
### Custom Timezone and Logging
```yaml
env:
@ -126,20 +56,110 @@ env:
value: "5055"
```
### Using Persistence
By default, persistence is enabled with a 1Gi volume:
```yaml
persistence:
enabled: true
size: 1Gi
```
You can also use an existing PVC:
### Using an Existing PVC
```yaml
persistence:
enabled: true
existingClaim: my-jellyseerr-pvc
```
## Parameters
### Global Parameters
| Name | Description | Default |
|------|-------------|---------|
| `nameOverride` | Override the release name | `""` |
| `fullnameOverride` | Fully override the release name | `""` |
### Image Parameters
| Name | Description | Default |
|------|-------------|---------|
| `image.repository` | Jellyseerr image repository | `ghcr.io/fallenbagel/jellyseerr` |
| `image.tag` | Image tag | `2.5.2` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
### Deployment Parameters
| Name | Description | Default |
|------|-------------|---------|
| `replicaCount` | Number of replicas | `1` |
| `revisionHistoryLimit` | Revisions to retain | `3` |
| `podSecurityContext.runAsNonRoot` | Run as non-root | `true` |
| `podSecurityContext.runAsUser` | User ID | `1000` |
| `podSecurityContext.fsGroup` | Filesystem group ID | `1000` |
| `nodeSelector` | Node selector | `{}` |
| `tolerations` | Tolerations | `[]` |
| `affinity` | Affinity rules | `{}` |
| `podAnnotations` | Pod annotations | `{}` |
### Service Parameters
| Name | Description | Default |
|------|-------------|---------|
| `service.type` | Service type | `ClusterIP` |
| `service.port` | Service port | `5055` |
### Ingress Parameters
| Name | Description | Default |
|------|-------------|---------|
| `ingress.enabled` | Enable ingress | `false` |
| `ingress.className` | Ingress class name | `""` |
| `ingress.annotations` | Ingress annotations | `{}` |
| `ingress.hosts` | Ingress hosts | See values.yaml |
| `ingress.tls` | TLS configuration | `[]` |
### Persistence Parameters
| Name | Description | Default |
|------|-------------|---------|
| `persistence.enabled` | Enable persistence | `true` |
| `persistence.existingClaim` | Use an existing PVC | `""` |
| `persistence.storageClass` | Storage class | `""` |
| `persistence.accessMode` | Access mode | `ReadWriteOnce` |
| `persistence.size` | PVC size | `1Gi` |
| `persistence.annotations` | PVC annotations | `{}` |
### Environment Variables
| Name | Description | Default |
|------|-------------|---------|
| `env` | Environment variables | See values.yaml |
| `extraEnv` | Additional environment variables | `[]` |
### Resource Parameters
| Name | Description | Default |
|------|-------------|---------|
| `resources` | Resource limits and requests | `{}` |
### Health Check Parameters
| Name | Description | Default |
|------|-------------|---------|
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.path` | Liveness probe path | `/api/v1/status` |
| `probes.liveness.initialDelaySeconds` | Liveness initial delay | `30` |
| `probes.liveness.periodSeconds` | Liveness period | `10` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.path` | Readiness probe path | `/api/v1/status` |
| `probes.readiness.initialDelaySeconds` | Readiness initial delay | `5` |
| `probes.readiness.periodSeconds` | Readiness period | `5` |
## Troubleshooting
- **Application not starting**: Check that persistence is enabled and the PVC is accessible
- **Timezone issues**: Set the `TZ` environment variable to your local timezone
```bash
kubectl logs deployment/jellyseerr -f
kubectl describe pod -l app.kubernetes.io/name=jellyseerr
```
## Links
- [Jellyseerr GitHub](https://github.com/fallenbagel/jellyseerr)
- [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/jellyseerr)

View File

@ -1,266 +1,40 @@
# Joplin Server Helm Chart
A Helm chart for deploying Joplin Server on Kubernetes - Note-taking and synchronization server.
A Helm chart for deploying [Joplin Server](https://github.com/laurent22/joplin) on Kubernetes.
## Introduction
This chart deploys [Joplin Server](https://github.com/laurent22/joplin) on a Kubernetes cluster using the Helm package manager. Joplin Server is the synchronization server for Joplin, allowing you to sync your notes across devices.
This chart deploys Joplin Server, the synchronization server for the Joplin note-taking application, on a Kubernetes cluster. Joplin Server allows syncing notes across devices and supports filesystem or S3 storage, email notifications, and an optional AI transcription service.
Source code can be found here:
- https://github.com/rtomik/helm-charts/tree/main/charts/joplin-server
Source code: https://github.com/rtomik/helm-charts/tree/main/charts/joplin-server
## Prerequisites
- Kubernetes 1.19+
- Helm 3.0+
- **External PostgreSQL database** (Required - Joplin Server does not support SQLite in production)
- PV provisioner support in the underlying infrastructure (if persistence is needed for file storage)
- **External PostgreSQL database** (required Joplin Server does not support SQLite)
- PV provisioner support (if using filesystem storage)
## Installing the Chart
To install the chart with the release name `joplin-server`:
```bash
$ helm repo add joplin-chart https://rtomik.github.io/helm-charts
$ helm install joplin-server joplin-chart/joplin-server
helm repo add rtomik https://rtomik.github.io/helm-charts
helm install joplin-server rtomik/joplin-server
```
> **Important**: You must configure PostgreSQL database settings before installation.
> **Important**: Configure PostgreSQL database settings before installation.
## Uninstalling the Chart
To uninstall/delete the `joplin-server` deployment:
```bash
$ helm uninstall joplin-server
helm uninstall joplin-server
```
## Parameters
### Global parameters
| Name | Description | Value |
|------------------------|------------------------------------------------|-------|
| `nameOverride` | String to partially override the release name | `""` |
| `fullnameOverride` | String to fully override the release name | `""` |
### Image parameters
| Name | Description | Value |
|-------------------------|-----------------------------------|--------------------|
| `image.repository` | Joplin Server image repository | `joplin/server` |
| `image.tag` | Joplin Server image tag | `latest` |
| `image.pullPolicy` | Joplin Server image pull policy | `IfNotPresent` |
### Deployment parameters
| Name | Description | Value |
|--------------------------------------|-----------------------------------------------|-----------|
| `replicaCount` | Number of Joplin Server replicas | `1` |
| `revisionHistoryLimit` | Number of revisions to retain for rollback | `3` |
| `podSecurityContext.runAsNonRoot` | Run containers as non-root user | `true` |
| `podSecurityContext.runAsUser` | User ID for the container | `1001` |
| `podSecurityContext.fsGroup` | Group ID for the container filesystem | `1001` |
| `containerSecurityContext` | Security context for the container | See values.yaml |
| `nodeSelector` | Node labels for pod assignment | `{}` |
| `tolerations` | Tolerations for pod assignment | `[]` |
| `affinity` | Affinity for pod assignment | `{}` |
### Service parameters
| Name | Description | Value |
|----------------|-----------------------|-------------|
| `service.type` | Kubernetes Service type | `ClusterIP` |
| `service.port` | Service HTTP port | `22300` |
### Ingress parameters
| Name | Description | Value |
|-------------------------|-------------------------------------------|-----------------|
| `ingress.enabled` | Enable ingress record generation | `false` |
| `ingress.className` | IngressClass name | `""` |
| `ingress.annotations` | Additional annotations for the Ingress | See values.yaml |
| `ingress.hosts` | Array of host and path objects | See values.yaml |
| `ingress.tls` | TLS configuration | See values.yaml |
### Environment variables
| Name | Description | Value |
|---------------------------|-----------------------------------------------|--------------------------|
| `env.APP_PORT` | Application port | `22300` |
| `env.APP_BASE_URL` | Base URL for the application | `http://localhost:22300` |
| `env.DB_CLIENT` | Database client (always pg for PostgreSQL) | `pg` |
### PostgreSQL configuration (Required)
| Name | Description | Value |
|----------------------------------------|-----------------------------------------------|-----------|
| `postgresql.external.enabled` | Use external PostgreSQL database (required) | `true` |
| `postgresql.external.host` | PostgreSQL host | `""` |
| `postgresql.external.port` | PostgreSQL port | `5432` |
| `postgresql.external.database` | PostgreSQL database name | `joplin` |
| `postgresql.external.user` | PostgreSQL username | `joplin` |
| `postgresql.external.password` | PostgreSQL password | `""` |
| `postgresql.external.existingSecret` | Name of existing secret with PostgreSQL credentials | `""` |
| `postgresql.external.userKey` | Key in the secret for username | `username` |
| `postgresql.external.passwordKey` | Key in the secret for password | `password` |
| `postgresql.external.hostKey` | Key in the secret for host (optional) | `""` |
| `postgresql.external.portKey` | Key in the secret for port (optional) | `""` |
| `postgresql.external.databaseKey` | Key in the secret for database name (optional) | `""` |
### Joplin Server Configuration
#### Admin Settings
| Name | Description | Value |
|----------------------------------------|-----------------------------------------------|-----------|
| `joplin.admin.email` | First admin user email | `""` |
| `joplin.admin.password` | First admin user password | `""` |
| `joplin.admin.existingSecret` | Name of existing secret with admin credentials | `""` |
| `joplin.admin.emailKey` | Key in the secret for admin email | `admin-email` |
| `joplin.admin.passwordKey` | Key in the secret for admin password | `admin-password` |
#### Server Settings
| Name | Description | Value |
|-------------------------------------------|-----------------------------------------------|-----------|
| `joplin.server.maxRequestBodySize` | Maximum request body size | `200mb` |
| `joplin.server.sessionTimeout` | Session timeout in seconds | `86400` |
| `joplin.server.enableUserRegistration` | Enable/disable user registration | `false` |
| `joplin.server.enableSharing` | Enable/disable sharing | `true` |
| `joplin.server.enablePublicNotes` | Enable/disable public notes | `true` |
#### Storage Settings
| Name | Description | Value |
|-------------------------------------------|-----------------------------------------------|--------------|
| `joplin.storage.driver` | Storage driver (filesystem, s3, azure) | `filesystem` |
| `joplin.storage.filesystemPath` | Path for filesystem storage | `/var/lib/joplin` |
##### S3 Storage (Optional)
| Name | Description | Value |
|---------------------------------------------|---------------------------------------------|-----------|
| `joplin.storage.s3.bucket` | S3 bucket name | `""` |
| `joplin.storage.s3.region` | S3 region | `""` |
| `joplin.storage.s3.accessKeyId` | S3 access key ID | `""` |
| `joplin.storage.s3.secretAccessKey` | S3 secret access key | `""` |
| `joplin.storage.s3.endpoint` | S3 endpoint (for S3-compatible services) | `""` |
| `joplin.storage.s3.existingSecret` | Name of existing secret with S3 credentials | `""` |
| `joplin.storage.s3.accessKeyIdKey` | Key in the secret for access key ID | `access-key-id` |
| `joplin.storage.s3.secretAccessKeyKey` | Key in the secret for secret access key | `secret-access-key` |
#### Email Settings (Optional)
| Name | Description | Value |
|----------------------------------------|-----------------------------------------------|-----------|
| `joplin.email.enabled` | Enable email notifications | `false` |
| `joplin.email.host` | SMTP host | `""` |
| `joplin.email.port` | SMTP port | `587` |
| `joplin.email.username` | SMTP username | `""` |
| `joplin.email.password` | SMTP password | `""` |
| `joplin.email.fromEmail` | From email address | `""` |
| `joplin.email.fromName` | From name | `Joplin Server` |
| `joplin.email.secure` | Use TLS/SSL | `true` |
| `joplin.email.existingSecret` | Name of existing secret with email credentials | `""` |
| `joplin.email.usernameKey` | Key in the secret for SMTP username | `email-username` |
| `joplin.email.passwordKey` | Key in the secret for SMTP password | `email-password` |
#### Logging Settings
| Name | Description | Value |
|--------------------------------|--------------------------------------|-----------|
| `joplin.logging.level` | Log level (error, warn, info, debug) | `info` |
| `joplin.logging.target` | Log target (console, file) | `console` |
### Persistence settings (for filesystem storage)
| Name | Description | Value |
|-------------------------------|----------------------------------|-----------------|
| `persistence.enabled` | Enable persistence using PVC | `true` |
| `persistence.storageClass` | PVC Storage Class | `""` |
| `persistence.accessMode` | PVC Access Mode | `ReadWriteOnce` |
| `persistence.size` | PVC Size | `10Gi` |
| `persistence.annotations` | Annotations for PVC | `{}` |
### Transcribe Service (Optional AI Transcription)
| Name | Description | Value |
|-------------------------------------------|-----------------------------------------------|--------------|
| `transcribe.enabled` | Enable transcribe service | `false` |
| `transcribe.image.repository` | Transcribe image repository | `joplin/transcribe` |
| `transcribe.image.tag` | Transcribe image tag | `latest` |
| `transcribe.image.pullPolicy` | Transcribe image pull policy | `IfNotPresent` |
| `transcribe.api.key` | Shared secret between Joplin and Transcribe | `""` |
| `transcribe.api.existingSecret` | Name of existing secret with transcribe API key | `""` |
| `transcribe.api.keyName` | Key in the secret for transcribe API key | `transcribe-api-key` |
| `transcribe.service.type` | Transcribe service type | `ClusterIP` |
| `transcribe.service.port` | Transcribe service port | `4567` |
| `transcribe.htr.imagesFolder` | HTR images folder path | `/app/images` |
#### Transcribe Database (Separate from main database)
| Name | Description | Value |
|---------------------------------------------|---------------------------------------------|-------------|
| `transcribe.database.host` | Transcribe database host | `""` |
| `transcribe.database.port` | Transcribe database port | `5432` |
| `transcribe.database.database` | Transcribe database name | `transcribe` |
| `transcribe.database.user` | Transcribe database username | `transcribe` |
| `transcribe.database.password` | Transcribe database password | `""` |
| `transcribe.database.existingSecret` | Name of existing secret with transcribe DB credentials | `""` |
| `transcribe.database.userKey` | Key in the secret for username | `username` |
| `transcribe.database.passwordKey` | Key in the secret for password | `password` |
### Resource Configuration
| Name | Description | Value |
|-------------|--------------------------------------|-------|
| `resources` | Resource limits and requests | `{}` |
### Health Checks
| Name | Description | Value |
|-------------------------------------------|------------------------------------------|-------|
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.initialDelaySeconds` | Initial delay for liveness probe | `60` |
| `probes.liveness.periodSeconds` | Period for liveness probe | `30` |
| `probes.liveness.timeoutSeconds` | Timeout for liveness probe | `10` |
| `probes.liveness.failureThreshold` | Failure threshold for liveness probe | `3` |
| `probes.liveness.successThreshold` | Success threshold for liveness probe | `1` |
| `probes.liveness.path` | Path for liveness probe | `/api/ping` |
| `probes.liveness.httpHeaders` | HTTP headers for liveness probe | `[{"name": "Host", "value": "joplin.domain.com"}]` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.initialDelaySeconds` | Initial delay for readiness probe | `30` |
| `probes.readiness.periodSeconds` | Period for readiness probe | `10` |
| `probes.readiness.timeoutSeconds` | Timeout for readiness probe | `5` |
| `probes.readiness.failureThreshold` | Failure threshold for readiness probe | `3` |
| `probes.readiness.successThreshold` | Success threshold for readiness probe | `1` |
| `probes.readiness.path` | Path for readiness probe | `/api/ping` |
| `probes.readiness.httpHeaders` | HTTP headers for readiness probe | `[{"name": "Host", "value": "joplin.domain.com"}]` |
### Autoscaling
| Name | Description | Value |
|---------------------------------------------|------------------------------------------|---------|
| `autoscaling.enabled` | Enable horizontal pod autoscaling | `false` |
| `autoscaling.minReplicas` | Minimum number of replicas | `1` |
| `autoscaling.maxReplicas` | Maximum number of replicas | `3` |
| `autoscaling.targetCPUUtilizationPercentage`| Target CPU utilization percentage | `80` |
| `autoscaling.targetMemoryUtilizationPercentage`| Target memory utilization percentage | `80` |
### Security Settings
| Name | Description | Value |
|-----------------------------------|--------------------------------------|---------|
| `security.httpsRedirect` | Enable/disable HTTPS redirect | `false` |
| `security.tls.enabled` | Enable custom TLS certificate | `false` |
| `security.tls.existingSecret` | Name of existing secret with TLS cert| `""` |
| `security.tls.certificateKey` | Key in the secret for TLS certificate| `tls.crt` |
| `security.tls.privateKeyKey` | Key in the secret for TLS private key| `tls.key` |
## Configuration Examples
### Basic Installation with PostgreSQL
### Minimal Installation
> **Important**: Health check probes require a `Host` header matching your ingress domain. Update `probes.*.httpHeaders` accordingly.
```yaml
postgresql:
@ -272,22 +46,9 @@ postgresql:
user: "joplin"
password: "secure-password"
ingress:
enabled: true
hosts:
- host: joplin.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- joplin.example.com
secretName: joplin-tls
env:
APP_BASE_URL: "https://joplin.example.com"
# IMPORTANT: Update health check host headers to match your domain
probes:
liveness:
httpHeaders:
@ -304,30 +65,22 @@ joplin:
password: "admin-password"
server:
enableUserRegistration: true
```
### Using Kubernetes Secrets
#### Full Secret Configuration
```yaml
postgresql:
external:
ingress:
enabled: true
existingSecret: "joplin-postgresql-secret"
hostKey: "host"
portKey: "port"
databaseKey: "database"
userKey: "username"
passwordKey: "password"
joplin:
admin:
existingSecret: "joplin-admin-secret"
emailKey: "email"
passwordKey: "password"
hosts:
- host: joplin.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- joplin.example.com
secretName: joplin-tls
```
#### Mixed Configuration (Host in values, credentials in secret)
### Production with Existing Secrets
```yaml
postgresql:
external:
@ -338,10 +91,15 @@ postgresql:
existingSecret: "joplin-db-credentials"
userKey: "username"
passwordKey: "password"
# hostKey, portKey, databaseKey left empty - using values above
joplin:
admin:
existingSecret: "joplin-admin-secret"
emailKey: "email"
passwordKey: "password"
```
### S3 Storage Configuration
### S3 Storage
```yaml
joplin:
@ -359,7 +117,7 @@ persistence:
enabled: false
```
### Email Notifications Setup
### Email Notifications
```yaml
joplin:
@ -375,7 +133,7 @@ joplin:
passwordKey: "password"
```
### Transcribe Service (AI Features)
### Transcribe Service (AI Transcription)
```yaml
transcribe:
@ -396,34 +154,208 @@ transcribe:
size: 5Gi
```
## First-time Setup
## Parameters
1. **Configure PostgreSQL**: Ensure your PostgreSQL database is accessible and credentials are configured
2. **Admin User**: Set admin email/password or access the web interface to create the first admin user
3. **User Registration**: Configure whether users can self-register or admin approval is required
4. **Storage**: Choose between filesystem (requires persistence) or cloud storage (S3/Azure)
### Global Parameters
## Security Considerations
| Name | Description | Default |
|------|-------------|---------|
| `nameOverride` | Override the release name | `""` |
| `fullnameOverride` | Fully override the release name | `""` |
For production deployments:
### Image Parameters
1. Use external secrets for all sensitive information (database passwords, admin credentials, etc.)
2. Enable TLS/SSL for all communications
3. Configure proper RBAC and network policies
4. Use dedicated databases with proper access controls
5. Disable user registration if not needed
6. Use cloud storage for better scalability and backup
| Name | Description | Default |
|------|-------------|---------|
| `image.repository` | Joplin Server image repository | `joplin/server` |
| `image.tag` | Image tag | `3.4.2` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
### Deployment Parameters
| Name | Description | Default |
|------|-------------|---------|
| `replicaCount` | Number of replicas | `1` |
| `revisionHistoryLimit` | Revisions to retain | `3` |
| `podSecurityContext.runAsNonRoot` | Run as non-root | `true` |
| `podSecurityContext.runAsUser` | User ID | `1001` |
| `podSecurityContext.fsGroup` | Filesystem group ID | `1001` |
| `nodeSelector` | Node selector | `{}` |
| `tolerations` | Tolerations | `[]` |
| `affinity` | Affinity rules | `{}` |
### Service Parameters
| Name | Description | Default |
|------|-------------|---------|
| `service.type` | Service type | `ClusterIP` |
| `service.port` | Service port | `22300` |
### Ingress Parameters
| Name | Description | Default |
|------|-------------|---------|
| `ingress.enabled` | Enable ingress | `false` |
| `ingress.className` | Ingress class name | `""` |
| `ingress.annotations` | Ingress annotations | See values.yaml |
| `ingress.hosts` | Ingress hosts | See values.yaml |
| `ingress.tls` | TLS configuration | See values.yaml |
### PostgreSQL Configuration (Required)
| Name | Description | Default |
|------|-------------|---------|
| `postgresql.external.enabled` | Use external PostgreSQL | `true` |
| `postgresql.external.host` | PostgreSQL host | `""` |
| `postgresql.external.port` | PostgreSQL port | `5432` |
| `postgresql.external.database` | Database name | `joplin` |
| `postgresql.external.user` | Username | `joplin` |
| `postgresql.external.password` | Password | `""` |
| `postgresql.external.existingSecret` | Existing secret name | `""` |
| `postgresql.external.userKey` | Key for username in secret | `username` |
| `postgresql.external.passwordKey` | Key for password in secret | `password` |
| `postgresql.external.hostKey` | Key for host in secret (optional) | `""` |
| `postgresql.external.portKey` | Key for port in secret (optional) | `""` |
| `postgresql.external.databaseKey` | Key for database in secret (optional) | `""` |
### Admin Settings
| Name | Description | Default |
|------|-------------|---------|
| `joplin.admin.email` | Admin user email | `""` |
| `joplin.admin.password` | Admin user password | `""` |
| `joplin.admin.existingSecret` | Existing secret for admin credentials | `""` |
| `joplin.admin.emailKey` | Key for email in secret | `admin-email` |
| `joplin.admin.passwordKey` | Key for password in secret | `admin-password` |
### Server Settings
| Name | Description | Default |
|------|-------------|---------|
| `joplin.server.maxRequestBodySize` | Max request body size | `200mb` |
| `joplin.server.sessionTimeout` | Session timeout (seconds) | `86400` |
| `joplin.server.enableUserRegistration` | Enable user registration | `false` |
| `joplin.server.enableSharing` | Enable sharing | `true` |
| `joplin.server.enablePublicNotes` | Enable public notes | `true` |
### Storage Settings
| Name | Description | Default |
|------|-------------|---------|
| `joplin.storage.driver` | Storage driver (`filesystem`, `s3`, `azure`) | `filesystem` |
| `joplin.storage.filesystemPath` | Filesystem storage path | `/var/lib/joplin` |
| `joplin.storage.s3.bucket` | S3 bucket name | `""` |
| `joplin.storage.s3.region` | S3 region | `""` |
| `joplin.storage.s3.endpoint` | S3 endpoint (for S3-compatible services) | `""` |
| `joplin.storage.s3.accessKeyId` | S3 access key ID | `""` |
| `joplin.storage.s3.secretAccessKey` | S3 secret access key | `""` |
| `joplin.storage.s3.existingSecret` | Existing secret for S3 credentials | `""` |
| `joplin.storage.s3.accessKeyIdKey` | Key for access key in secret | `access-key-id` |
| `joplin.storage.s3.secretAccessKeyKey` | Key for secret access key in secret | `secret-access-key` |
### Email Settings
| Name | Description | Default |
|------|-------------|---------|
| `joplin.email.enabled` | Enable email | `false` |
| `joplin.email.host` | SMTP host | `""` |
| `joplin.email.port` | SMTP port | `587` |
| `joplin.email.username` | SMTP username | `""` |
| `joplin.email.password` | SMTP password | `""` |
| `joplin.email.fromEmail` | From email address | `""` |
| `joplin.email.fromName` | From name | `Joplin Server` |
| `joplin.email.secure` | Use TLS/SSL | `true` |
| `joplin.email.existingSecret` | Existing secret for credentials | `""` |
| `joplin.email.usernameKey` | Key for username in secret | `email-username` |
| `joplin.email.passwordKey` | Key for password in secret | `email-password` |
### Logging Settings
| Name | Description | Default |
|------|-------------|---------|
| `joplin.logging.level` | Log level (`error`, `warn`, `info`, `debug`) | `info` |
| `joplin.logging.target` | Log target (`console`, `file`) | `console` |
### Persistence Parameters
| Name | Description | Default |
|------|-------------|---------|
| `persistence.enabled` | Enable persistence | `true` |
| `persistence.storageClass` | Storage class | `""` |
| `persistence.accessMode` | Access mode | `ReadWriteOnce` |
| `persistence.size` | PVC size | `10Gi` |
| `persistence.annotations` | PVC annotations | `{}` |
### Transcribe Service
| Name | Description | Default |
|------|-------------|---------|
| `transcribe.enabled` | Enable transcribe service | `false` |
| `transcribe.image.repository` | Transcribe image repository | `joplin/transcribe` |
| `transcribe.image.tag` | Transcribe image tag | `latest` |
| `transcribe.api.key` | Shared API key | `""` |
| `transcribe.api.existingSecret` | Existing secret for API key | `""` |
| `transcribe.api.keyName` | Key name in secret | `transcribe-api-key` |
| `transcribe.service.type` | Transcribe service type | `ClusterIP` |
| `transcribe.service.port` | Transcribe service port | `4567` |
| `transcribe.database.host` | Transcribe DB host | `""` |
| `transcribe.database.port` | Transcribe DB port | `5432` |
| `transcribe.database.database` | Transcribe DB name | `transcribe` |
| `transcribe.database.user` | Transcribe DB user | `transcribe` |
| `transcribe.database.password` | Transcribe DB password | `""` |
| `transcribe.database.existingSecret` | Existing secret for transcribe DB | `""` |
| `transcribe.database.userKey` | Key for username in secret | `username` |
| `transcribe.database.passwordKey` | Key for password in secret | `password` |
### Security Settings
| Name | Description | Default |
|------|-------------|---------|
| `security.httpsRedirect` | Enable HTTPS redirect | `false` |
| `security.tls.enabled` | Enable custom TLS certificate | `false` |
| `security.tls.existingSecret` | Secret with TLS certificate | `""` |
| `security.tls.certificateKey` | Key for TLS certificate in secret | `tls.crt` |
| `security.tls.privateKeyKey` | Key for TLS private key in secret | `tls.key` |
### Resource Parameters
| Name | Description | Default |
|------|-------------|---------|
| `resources` | Resource limits and requests | `{}` |
### Health Check Parameters
| Name | Description | Default |
|------|-------------|---------|
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.path` | Liveness probe path | `/api/ping` |
| `probes.liveness.initialDelaySeconds` | Liveness initial delay | `60` |
| `probes.liveness.periodSeconds` | Liveness period | `30` |
| `probes.liveness.httpHeaders` | Liveness HTTP headers | Host matching ingress |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.path` | Readiness probe path | `/api/ping` |
| `probes.readiness.initialDelaySeconds` | Readiness initial delay | `30` |
| `probes.readiness.periodSeconds` | Readiness period | `10` |
| `probes.readiness.httpHeaders` | Readiness HTTP headers | Host matching ingress |
### Autoscaling Parameters
| Name | Description | Default |
|------|-------------|---------|
| `autoscaling.enabled` | Enable HPA | `false` |
| `autoscaling.minReplicas` | Min replicas | `1` |
| `autoscaling.maxReplicas` | Max replicas | `3` |
| `autoscaling.targetCPUUtilizationPercentage` | Target CPU | `80` |
| `autoscaling.targetMemoryUtilizationPercentage` | Target memory | `80` |
## Troubleshooting
Common issues and solutions:
### Health Check Failures / "No Available Server"
1. **Health Check Issues / "No Available Server"**:
- Ensure `probes.*.httpHeaders` includes the correct Host header matching your domain
- Health checks use `/api/ping` endpoint which requires proper host validation
- Example fix:
```yaml
probes:
Health checks require the correct `Host` header matching your ingress domain:
```yaml
probes:
liveness:
httpHeaders:
- name: Host
@ -432,29 +364,24 @@ Common issues and solutions:
httpHeaders:
- name: Host
value: your-joplin-domain.com
```
```
2. **Database connection issues**: Verify PostgreSQL credentials and network connectivity
3. **Storage permissions**: Check filesystem permissions for persistent volumes
4. **First admin user**: If no admin configured, access the web interface to create one
5. **Transcribe issues**: Verify Docker socket access and separate database configuration
6. **Origin validation errors**: Make sure `env.APP_BASE_URL` matches your ingress host
### Database Connection Issues
For detailed troubleshooting, check the application logs:
Verify PostgreSQL credentials, network connectivity, and that `env.APP_BASE_URL` matches your ingress host.
### Origin Validation Errors
Ensure `env.APP_BASE_URL` matches your ingress hostname exactly.
### Debugging
```bash
kubectl logs -f deployment/joplin-server
```
Check pod status and events:
```bash
kubectl describe pod -l app.kubernetes.io/name=joplin-server
```
## Backing Up
## Links
- **Database**: Use PostgreSQL backup tools (pg_dump, etc.)
- **File Storage**:
- Filesystem: Backup the PVC data
- S3: Files are already stored in S3 (ensure proper S3 backup policies)
- **Configuration**: Backup your Kubernetes secrets and config
- [Joplin GitHub](https://github.com/laurent22/joplin)
- [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/joplin-server)

View File

@ -1,14 +1,18 @@
# Karakeep Helm Chart
This Helm chart deploys [Karakeep](https://github.com/karakeep-app/karakeep), a bookmark management application, along with its required services on a Kubernetes cluster.
A Helm chart for deploying [Karakeep](https://github.com/karakeep-app/karakeep), a bookmark management application, on Kubernetes.
## Components
## Introduction
This chart deploys three containers in a single pod:
This chart deploys Karakeep as a multi-container pod with three services:
1. **Karakeep**: The main bookmark management application
2. **Chrome**: Headless Chrome browser for web scraping and preview generation
3. **MeiliSearch**: Search engine for fast bookmark search functionality
1. **Karakeep** — Main bookmark management application
2. **Chrome** Headless browser for web scraping and preview generation
3. **MeiliSearch** Search engine for fast bookmark search
All containers share the same pod network and communicate via localhost.
Source code: https://github.com/rtomik/helm-charts/tree/main/charts/karakeep
## Prerequisites
@ -18,91 +22,180 @@ This chart deploys three containers in a single pod:
## Installing the Chart
To install the chart with the release name `karakeep`:
```bash
helm repo add karakeep-chart https://rtomik.github.io/helm-charts
helm install karakeep karakeep-chart/karakeep
helm repo add rtomik https://rtomik.github.io/helm-charts
helm install karakeep rtomik/karakeep
```
## Uninstalling the Chart
To uninstall/delete the `karakeep` deployment:
```bash
helm delete karakeep
helm uninstall karakeep
```
## Configuration
## Configuration Examples
The following table lists the configurable parameters and their default values.
### Minimal Installation
### Global Settings
```yaml
ingress:
enabled: true
hosts:
- host: karakeep.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- karakeep.example.com
```
| Parameter | Description | Default |
|-----------|-------------|---------|
| `nameOverride` | Override the name of the chart | `""` |
| `fullnameOverride` | Override the full name of the chart | `""` |
### Production with Secrets
For production, store `NEXTAUTH_SECRET` in a Kubernetes secret. When ingress is enabled, `NEXTAUTH_URL` is automatically set to the ingress hostname.
```yaml
secrets:
create: true
env:
NEXTAUTH_SECRET: "your-secure-32-character-string"
ingress:
enabled: true
hosts:
- host: karakeep.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- karakeep.example.com
```
### With OpenAI Integration
```yaml
secrets:
create: true
env:
NEXTAUTH_SECRET: "your-secure-32-character-string"
OPENAI_API_KEY: "your-openai-api-key"
```
## Parameters
### Global Parameters
| Name | Description | Default |
|------|-------------|---------|
| `nameOverride` | Override the chart name | `""` |
| `fullnameOverride` | Override the full chart name | `""` |
| `replicaCount` | Number of replicas | `1` |
| `revisionHistoryLimit` | Revisions to retain | `3` |
### Karakeep Configuration
### Pod Security Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| Name | Description | Default |
|------|-------------|---------|
| `podSecurityContext.runAsNonRoot` | Run as non-root | `false` |
| `podSecurityContext.runAsUser` | User ID | `0` |
| `podSecurityContext.fsGroup` | Filesystem group ID | `0` |
### Karakeep Parameters
| Name | Description | Default |
|------|-------------|---------|
| `karakeep.image.repository` | Karakeep image repository | `ghcr.io/karakeep-app/karakeep` |
| `karakeep.image.tag` | Karakeep image tag | `"release"` |
| `karakeep.image.tag` | Karakeep image tag | `0.26.0` |
| `karakeep.image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `karakeep.service.port` | Karakeep service port | `3000` |
| `karakeep.env` | Karakeep environment variables | See values.yaml |
| `karakeep.extraEnv` | Additional environment variables | `[]` |
### Chrome Configuration
### Chrome Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| Name | Description | Default |
|------|-------------|---------|
| `chrome.image.repository` | Chrome image repository | `gcr.io/zenika-hub/alpine-chrome` |
| `chrome.image.tag` | Chrome image tag | `"124"` |
| `chrome.image.tag` | Chrome image tag | `124` |
| `chrome.image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `chrome.service.port` | Chrome debugging port | `9222` |
### MeiliSearch Configuration
### MeiliSearch Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| Name | Description | Default |
|------|-------------|---------|
| `meilisearch.image.repository` | MeiliSearch image repository | `getmeili/meilisearch` |
| `meilisearch.image.tag` | MeiliSearch image tag | `"v1.13.3"` |
| `meilisearch.image.tag` | MeiliSearch image tag | `v1.13.3` |
| `meilisearch.image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `meilisearch.service.port` | MeiliSearch port | `7700` |
| `meilisearch.resources.limits.cpu` | CPU limit | `500m` |
| `meilisearch.resources.limits.memory` | Memory limit | `1Gi` |
| `meilisearch.resources.requests.cpu` | CPU request | `100m` |
| `meilisearch.resources.requests.memory` | Memory request | `256Mi` |
### Persistence
### Service Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `persistence.enabled` | Enable persistent storage | `true` |
| `persistence.data.size` | Size of data volume | `5Gi` |
| `persistence.data.storageClass` | Storage class for data volume | `""` |
| `persistence.meilisearch.size` | Size of MeiliSearch volume | `2Gi` |
| `persistence.meilisearch.storageClass` | Storage class for MeiliSearch volume | `""` |
| Name | Description | Default |
|------|-------------|---------|
| `service.type` | Service type | `ClusterIP` |
| `service.port` | Service port | `3000` |
### Ingress
### Ingress Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| Name | Description | Default |
|------|-------------|---------|
| `ingress.enabled` | Enable ingress | `false` |
| `ingress.hosts[0].host` | Hostname | `karakeep.domain.com` |
| `ingress.className` | Ingress class name | `""` |
| `ingress.annotations` | Ingress annotations | See values.yaml |
| `ingress.hosts` | Ingress hosts | See values.yaml |
| `ingress.tls` | TLS configuration | See values.yaml |
### Secrets
### Persistence Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| Name | Description | Default |
|------|-------------|---------|
| `persistence.enabled` | Enable persistence | `true` |
| `persistence.data.storageClass` | Data volume storage class | `""` |
| `persistence.data.accessMode` | Data volume access mode | `ReadWriteOnce` |
| `persistence.data.size` | Data volume size | `5Gi` |
| `persistence.meilisearch.storageClass` | MeiliSearch volume storage class | `""` |
| `persistence.meilisearch.accessMode` | MeiliSearch volume access mode | `ReadWriteOnce` |
| `persistence.meilisearch.size` | MeiliSearch volume size | `2Gi` |
### Secret Parameters
| Name | Description | Default |
|------|-------------|---------|
| `secrets.create` | Create secret for environment variables | `false` |
| `secrets.existingSecret` | Use existing secret | `""` |
| `secrets.env` | Environment variables to store in secret | `{}` |
| `secrets.existingSecret` | Use an existing secret | `""` |
| `secrets.env` | Environment variables for the secret | `{}` |
**Important Configuration:**
1. The default `NEXTAUTH_SECRET` is set to a placeholder value. For production deployments, you should either:
- Override the value: `--set karakeep.env[3].value="your-secure-32-character-string"`
- Use secrets: `--set secrets.create=true --set secrets.env.NEXTAUTH_SECRET="your-secure-32-character-string"`
## Troubleshooting
2. When ingress is enabled, `NEXTAUTH_URL` is automatically set to the ingress hostname. For custom configurations:
- Override manually: `--set karakeep.env[4].value="https://your-domain.com"`
### NEXTAUTH_SECRET Not Set
## Notes
The default `NEXTAUTH_SECRET` is a placeholder. For production, override it:
- This chart creates a multi-container pod with all three services running together
- Data persistence is enabled by default with separate volumes for Karakeep data and MeiliSearch indices
- The services communicate via localhost since they share the same pod network
- Chrome runs with security flags for containerized environments
```yaml
secrets:
create: true
env:
NEXTAUTH_SECRET: "your-secure-32-character-string"
```
### Custom NEXTAUTH_URL
If not using ingress or using a custom domain, override `NEXTAUTH_URL` manually:
```yaml
karakeep:
env:
- name: NEXTAUTH_URL
value: "https://your-domain.com"
```
## Links
- [Karakeep GitHub](https://github.com/karakeep-app/karakeep)
- [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/karakeep)

View File

@ -1,273 +1,41 @@
# Mealie Helm Chart
A Helm chart for deploying Mealie recipe management and meal planning application on Kubernetes.
A Helm chart for deploying [Mealie](https://github.com/mealie-recipes/mealie), a recipe management and meal planning application, on Kubernetes.
## Introduction
This chart deploys [Mealie](https://github.com/mealie-recipes/mealie) on a Kubernetes cluster using the Helm package manager. Mealie is a self-hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in Vue for a pleasant user experience for the whole family.
This chart deploys Mealie on a Kubernetes cluster using the Helm package manager. Mealie is a self-hosted recipe manager with a reactive frontend and RestAPI backend, supporting PostgreSQL or SQLite databases, LDAP/OIDC authentication, and OpenAI integration.
Source code can be found here:
- https://github.com/rtomik/helm-charts/tree/main/charts/mealie
Source code: https://github.com/rtomik/helm-charts/tree/main/charts/mealie
## Prerequisites
- Kubernetes 1.19+
- Helm 3.0+
- PV provisioner support in the underlying infrastructure (if persistence is needed)
- External Postgresql DB like https://cloudnative-pg.io/
- External PostgreSQL database (recommended, e.g. [CloudNativePG](https://cloudnative-pg.io/))
- PV provisioner support (if persistence is needed)
## Installing the Chart
To install the chart with the release name `mealie`:
```bash
$ helm repo add mealie-chart https://rtomik.github.io/helm-charts
$ helm install mealie mealie-chart/mealie
helm repo add rtomik https://rtomik.github.io/helm-charts
helm install mealie rtomik/mealie
```
> **Tip**: List all releases using `helm list`
## Uninstalling the Chart
To uninstall/delete the `mealie` deployment:
```bash
$ helm uninstall mealie
helm uninstall mealie
```
## Parameters
### Global parameters
| Name | Description | Value |
|------------------------|------------------------------------------------|-------|
| `nameOverride` | String to partially override the release name | `""` |
| `fullnameOverride` | String to fully override the release name | `""` |
### Image parameters
| Name | Description | Value |
|-------------------------|-----------------------------------|-----------------------------------|
| `image.repository` | Mealie image repository | `ghcr.io/mealie-recipes/mealie` |
| `image.tag` | Mealie image tag | `v3.1.1` |
| `image.pullPolicy` | Mealie image pull policy | `IfNotPresent` |
### Deployment parameters
| Name | Description | Value |
|--------------------------------------|-----------------------------------------------|-----------|
| `replicaCount` | Number of Mealie replicas | `1` |
| `revisionHistoryLimit` | Number of revisions to retain for rollback | `3` |
| `podSecurityContext.runAsNonRoot` | Run containers as non-root user | `false` |
| `podSecurityContext.runAsUser` | User ID for the container | `911` |
| `podSecurityContext.fsGroup` | Group ID for the container filesystem | `911` |
| `containerSecurityContext` | Security context for the container | See values.yaml |
| `nodeSelector` | Node labels for pod assignment | `{}` |
| `tolerations` | Tolerations for pod assignment | `[]` |
| `affinity` | Affinity for pod assignment | `{}` |
### Service parameters
| Name | Description | Value |
|----------------|-----------------------|-------------|
| `service.type` | Kubernetes Service type | `ClusterIP` |
| `service.port` | Service HTTP port | `9000` |
### Ingress parameters
| Name | Description | Value |
|-------------------------|-------------------------------------------|-----------------|
| `ingress.enabled` | Enable ingress record generation | `false` |
| `ingress.className` | IngressClass name | `""` |
| `ingress.annotations` | Additional annotations for the Ingress | See values.yaml |
| `ingress.hosts` | Array of host and path objects | See values.yaml |
| `ingress.tls` | TLS configuration | See values.yaml |
### Persistence parameters
| Name | Description | Value |
|-------------------------------|----------------------------------|-----------------|
| `persistence.enabled` | Enable persistence using PVC | `true` |
| `persistence.storageClass` | PVC Storage Class | `""` |
| `persistence.accessMode` | PVC Access Mode | `ReadWriteOnce` |
| `persistence.size` | PVC Size | `5Gi` |
| `persistence.annotations` | Annotations for PVC | `{}` |
### Environment variables
| Name | Description | Value |
|---------------------------------------|-----------------------------------------------|-----------------|
| `env.PUID` | UserID permissions between host OS and container | `911` |
| `env.PGID` | GroupID permissions between host OS and container | `911` |
| `env.DEFAULT_GROUP` | The default group for users | `Home` |
| `env.DEFAULT_HOUSEHOLD` | The default household for users in each group | `Family` |
| `env.BASE_URL` | Used for Notifications | `http://localhost:9000` |
| `env.TOKEN_TIME` | The time in hours that a login/auth token is valid | `48` |
| `env.API_PORT` | The port exposed by backend API | `9000` |
| `env.API_DOCS` | Turns on/off access to the API documentation | `true` |
| `env.TZ` | Must be set to get correct date/time on the server | `UTC` |
| `env.ALLOW_SIGNUP` | Allow user sign-up without token | `false` |
| `env.ALLOW_PASSWORD_LOGIN` | Whether or not to display username+password input fields | `true` |
| `env.LOG_LEVEL` | Logging level | `info` |
| `env.DAILY_SCHEDULE_TIME` | Time to run daily server tasks (HH:MM) | `23:45` |
### PostgreSQL configuration
| Name | Description | Value |
|----------------------------------------|-----------------------------------------------|-----------|
| `postgresql.enabled` | Enable PostgreSQL support | `false` |
| `postgresql.external.enabled` | Use external PostgreSQL database | `false` |
| `postgresql.external.host` | PostgreSQL host | `""` |
| `postgresql.external.port` | PostgreSQL port | `5432` |
| `postgresql.external.database` | PostgreSQL database name | `mealie` |
| `postgresql.external.user` | PostgreSQL username | `mealie` |
| `postgresql.external.password` | PostgreSQL password | `""` |
| `postgresql.external.existingSecret` | Name of existing secret with PostgreSQL credentials | `""` |
| `postgresql.external.userKey` | Key in the secret for username | `username` |
| `postgresql.external.passwordKey` | Key in the secret for password | `password` |
### Email (SMTP) configuration
| Name | Description | Value |
|--------------------------|--------------------------------------|-----------|
| `email.enabled` | Enable SMTP email support | `false` |
| `email.host` | SMTP host | `""` |
| `email.port` | SMTP port | `587` |
| `email.fromName` | From name for emails | `Mealie` |
| `email.authStrategy` | SMTP auth strategy (TLS, SSL, NONE) | `TLS` |
| `email.fromEmail` | From email address | `""` |
| `email.user` | SMTP username | `""` |
| `email.password` | SMTP password | `""` |
| `email.existingSecret` | Name of existing secret with SMTP credentials | `""` |
| `email.userKey` | Key in the secret for SMTP username | `smtp-user` |
| `email.passwordKey` | Key in the secret for SMTP password | `smtp-password` |
### LDAP Authentication
| Name | Description | Value |
|--------------------------|--------------------------------------|-----------|
| `ldap.enabled` | Enable LDAP authentication | `false` |
| `ldap.serverUrl` | LDAP server URL | `""` |
| `ldap.tlsInsecure` | Do not verify server certificate | `false` |
| `ldap.tlsCaCertFile` | Path to CA certificate file | `""` |
| `ldap.enableStartTls` | Use STARTTLS to connect to server | `false` |
| `ldap.baseDn` | Starting point for user authentication | `""` |
| `ldap.queryBind` | Optional bind user for LDAP searches | `""` |
| `ldap.queryPassword` | Password for the bind user | `""` |
| `ldap.userFilter` | LDAP filter to narrow down eligible users | `""` |
| `ldap.adminFilter` | LDAP filter for admin users | `""` |
| `ldap.idAttribute` | LDAP attribute for user ID | `uid` |
| `ldap.nameAttribute` | LDAP attribute for user name | `name` |
| `ldap.mailAttribute` | LDAP attribute for user email | `mail` |
### OpenID Connect (OIDC)
| Name | Description | Value |
|------------------------------|------------------------------------------|-----------|
| `oidc.enabled` | Enable OIDC authentication | `false` |
| `oidc.signupEnabled` | Allow new users via OIDC | `true` |
| `oidc.configurationUrl` | URL to OIDC configuration | `""` |
| `oidc.clientId` | OIDC client ID | `""` |
| `oidc.clientSecret` | OIDC client secret | `""` |
| `oidc.userGroup` | Required OIDC user group | `""` |
| `oidc.adminGroup` | OIDC admin group | `""` |
| `oidc.autoRedirect` | Bypass login page and redirect to IdP | `false` |
| `oidc.providerName` | Provider name shown in login button | `OAuth` |
| `oidc.rememberMe` | Extend session as if "Remember Me" was checked | `false` |
| `oidc.signingAlgorithm` | Algorithm used to sign the id token | `RS256` |
| `oidc.userClaim` | Claim to look up existing user by | `email` |
| `oidc.nameClaim` | Claim for user's full name | `name` |
| `oidc.groupsClaim` | Claim for user groups | `groups` |
### OpenAI Integration
| Name | Description | Value |
|------------------------------------|------------------------------------------|-----------|
| `openai.enabled` | Enable OpenAI integration | `false` |
| `openai.baseUrl` | Base URL for OpenAI API | `""` |
| `openai.apiKey` | OpenAI API key | `""` |
| `openai.model` | OpenAI model to use | `gpt-4o` |
| `openai.customHeaders` | Custom HTTP headers for OpenAI requests | `""` |
| `openai.customParams` | Custom HTTP query params for OpenAI requests | `""` |
| `openai.enableImageServices` | Enable OpenAI image services | `true` |
| `openai.workers` | Number of OpenAI workers per request | `2` |
| `openai.sendDatabaseData` | Send Mealie data to OpenAI to improve accuracy | `true` |
| `openai.requestTimeout` | Timeout for OpenAI requests in seconds | `60` |
### TLS Configuration
| Name | Description | Value |
|--------------------------|--------------------------------------|-----------|
| `tls.enabled` | Enable TLS configuration | `false` |
| `tls.certificatePath` | Path to TLS certificate file | `""` |
| `tls.privateKeyPath` | Path to TLS private key file | `""` |
| `tls.existingSecret` | Name of existing secret with TLS certificates | `""` |
| `tls.certificateKey` | Key in the secret for TLS certificate | `tls.crt` |
| `tls.privateKeyKey` | Key in the secret for TLS private key | `tls.key` |
### Theme Configuration
| Name | Description | Value |
|-------------------------------|--------------------------------|-----------|
| `theme.light.primary` | Light theme primary color | `#E58325` |
| `theme.light.accent` | Light theme accent color | `#007A99` |
| `theme.light.secondary` | Light theme secondary color | `#973542` |
| `theme.light.success` | Light theme success color | `#43A047` |
| `theme.light.info` | Light theme info color | `#1976D2` |
| `theme.light.warning` | Light theme warning color | `#FF6D00` |
| `theme.light.error` | Light theme error color | `#EF5350` |
| `theme.dark.primary` | Dark theme primary color | `#E58325` |
| `theme.dark.accent` | Dark theme accent color | `#007A99` |
| `theme.dark.secondary` | Dark theme secondary color | `#973542` |
| `theme.dark.success` | Dark theme success color | `#43A047` |
| `theme.dark.info` | Dark theme info color | `#1976D2` |
| `theme.dark.warning` | Dark theme warning color | `#FF6D00` |
| `theme.dark.error` | Dark theme error color | `#EF5350` |
### Resource Configuration
| Name | Description | Value |
|-------------|--------------------------------------|-------|
| `resources` | Resource limits and requests | `{}` |
### Health Checks
| Name | Description | Value |
|-------------------------------------------|------------------------------------------|-------|
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.initialDelaySeconds` | Initial delay for liveness probe | `60` |
| `probes.liveness.periodSeconds` | Period for liveness probe | `30` |
| `probes.liveness.timeoutSeconds` | Timeout for liveness probe | `10` |
| `probes.liveness.failureThreshold` | Failure threshold for liveness probe | `3` |
| `probes.liveness.successThreshold` | Success threshold for liveness probe | `1` |
| `probes.liveness.path` | Path for liveness probe | `/` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.initialDelaySeconds` | Initial delay for readiness probe | `30` |
| `probes.readiness.periodSeconds` | Period for readiness probe | `10` |
| `probes.readiness.timeoutSeconds` | Timeout for readiness probe | `5` |
| `probes.readiness.failureThreshold` | Failure threshold for readiness probe | `3` |
| `probes.readiness.successThreshold` | Success threshold for readiness probe | `1` |
| `probes.readiness.path` | Path for readiness probe | `/` |
### Autoscaling
| Name | Description | Value |
|---------------------------------------------|------------------------------------------|---------|
| `autoscaling.enabled` | Enable horizontal pod autoscaling | `false` |
| `autoscaling.minReplicas` | Minimum number of replicas | `1` |
| `autoscaling.maxReplicas` | Maximum number of replicas | `3` |
| `autoscaling.targetCPUUtilizationPercentage`| Target CPU utilization percentage | `80` |
| `autoscaling.targetMemoryUtilizationPercentage`| Target memory utilization percentage | `80` |
## Configuration Examples
### Basic Installation with Persistence
### Minimal Installation
```yaml
persistence:
enabled: true
size: 10Gi
storageClass: "fast-ssd"
ingress:
enabled: true
@ -282,7 +50,7 @@ ingress:
secretName: mealie-tls
```
### PostgreSQL Database Configuration
### PostgreSQL Configuration
```yaml
postgresql:
@ -300,7 +68,7 @@ env:
DB_ENGINE: "postgres"
```
### OIDC Authentication Setup
### OIDC Authentication
```yaml
oidc:
@ -326,27 +94,273 @@ openai:
enableImageServices: true
```
## Security Considerations
### LDAP Authentication
For production deployments, it's recommended to:
```yaml
ldap:
enabled: true
serverUrl: "ldap://ldap.example.com"
baseDn: "ou=users,dc=example,dc=com"
queryBind: "cn=admin,dc=example,dc=com"
queryPassword: "bind-password"
userFilter: "(objectClass=inetOrgPerson)"
idAttribute: "uid"
nameAttribute: "name"
mailAttribute: "mail"
```
1. Use external secrets for sensitive information
2. Enable TLS/SSL for all communications
3. Configure proper RBAC and network policies
4. Use a dedicated database with proper access controls
5. Enable authentication (LDAP/OIDC) and disable public signup
### Email Configuration
```yaml
email:
enabled: true
host: "smtp.example.com"
port: 587
fromName: "Mealie"
fromEmail: "mealie@example.com"
authStrategy: "TLS"
existingSecret: "mealie-smtp-secret"
userKey: "smtp-user"
passwordKey: "smtp-password"
```
## Parameters
### Global Parameters
| Name | Description | Default |
|------|-------------|---------|
| `nameOverride` | Override the release name | `""` |
| `fullnameOverride` | Fully override the release name | `""` |
### Image Parameters
| Name | Description | Default |
|------|-------------|---------|
| `image.repository` | Mealie image repository | `ghcr.io/mealie-recipes/mealie` |
| `image.tag` | Image tag | `v3.2.1` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
### Deployment Parameters
| Name | Description | Default |
|------|-------------|---------|
| `replicaCount` | Number of replicas | `1` |
| `revisionHistoryLimit` | Revisions to retain | `3` |
| `podSecurityContext.runAsNonRoot` | Run as non-root | `false` |
| `podSecurityContext.runAsUser` | User ID | `911` |
| `podSecurityContext.fsGroup` | Filesystem group ID | `911` |
| `nodeSelector` | Node selector | `{}` |
| `tolerations` | Tolerations | `[]` |
| `affinity` | Affinity rules | `{}` |
### Service Parameters
| Name | Description | Default |
|------|-------------|---------|
| `service.type` | Service type | `ClusterIP` |
| `service.port` | Service port | `9000` |
### Ingress Parameters
| Name | Description | Default |
|------|-------------|---------|
| `ingress.enabled` | Enable ingress | `false` |
| `ingress.className` | Ingress class name | `""` |
| `ingress.annotations` | Ingress annotations | See values.yaml |
| `ingress.hosts` | Ingress hosts | See values.yaml |
| `ingress.tls` | TLS configuration | See values.yaml |
### Persistence Parameters
| Name | Description | Default |
|------|-------------|---------|
| `persistence.enabled` | Enable persistence | `false` |
| `persistence.storageClass` | Storage class | `""` |
| `persistence.accessMode` | Access mode | `ReadWriteOnce` |
| `persistence.size` | PVC size | `5Gi` |
| `persistence.annotations` | PVC annotations | `{}` |
### Environment Variables
| Name | Description | Default |
|------|-------------|---------|
| `env.PUID` | User ID for host permissions | `911` |
| `env.PGID` | Group ID for host permissions | `911` |
| `env.DEFAULT_GROUP` | Default group for users | `Home` |
| `env.DEFAULT_HOUSEHOLD` | Default household | `Family` |
| `env.BASE_URL` | Base URL for notifications | `http://localhost:9000` |
| `env.TOKEN_TIME` | Login token validity (hours) | `48` |
| `env.API_PORT` | Backend API port | `9000` |
| `env.API_DOCS` | Enable API documentation | `true` |
| `env.TZ` | Timezone | `UTC` |
| `env.ALLOW_SIGNUP` | Allow user sign-up | `false` |
| `env.ALLOW_PASSWORD_LOGIN` | Allow password login | `true` |
| `env.LOG_LEVEL` | Log level | `info` |
| `env.DAILY_SCHEDULE_TIME` | Daily task schedule (HH:MM) | `23:45` |
| `env.DB_ENGINE` | Database engine (`postgres` or `sqlite`) | `postgres` |
| `extraEnv` | Additional environment variables | `[]` |
### PostgreSQL Configuration
| Name | Description | Default |
|------|-------------|---------|
| `postgresql.enabled` | Enable PostgreSQL | `false` |
| `postgresql.external.enabled` | Use external PostgreSQL | `false` |
| `postgresql.external.host` | PostgreSQL host | `""` |
| `postgresql.external.port` | PostgreSQL port | `5432` |
| `postgresql.external.database` | Database name | `mealie` |
| `postgresql.external.user` | Username | `mealie` |
| `postgresql.external.password` | Password | `""` |
| `postgresql.external.existingSecret` | Existing secret name | `""` |
| `postgresql.external.userKey` | Key for username in secret | `username` |
| `postgresql.external.passwordKey` | Key for password in secret | `password` |
### LDAP Authentication
| Name | Description | Default |
|------|-------------|---------|
| `ldap.enabled` | Enable LDAP | `false` |
| `ldap.serverUrl` | LDAP server URL | `""` |
| `ldap.tlsInsecure` | Skip server cert verification | `false` |
| `ldap.tlsCaCertFile` | CA certificate path | `""` |
| `ldap.enableStartTls` | Use STARTTLS | `false` |
| `ldap.baseDn` | Base DN for authentication | `""` |
| `ldap.queryBind` | Bind user for searches | `""` |
| `ldap.queryPassword` | Bind user password | `""` |
| `ldap.userFilter` | User LDAP filter | `""` |
| `ldap.adminFilter` | Admin LDAP filter | `""` |
| `ldap.idAttribute` | User ID attribute | `uid` |
| `ldap.nameAttribute` | User name attribute | `name` |
| `ldap.mailAttribute` | User email attribute | `mail` |
| `ldap.existingSecret` | Existing secret for LDAP | `""` |
| `ldap.passwordKey` | Key for password in secret | `ldap-password` |
### OIDC Authentication
| Name | Description | Default |
|------|-------------|---------|
| `oidc.enabled` | Enable OIDC | `false` |
| `oidc.signupEnabled` | Allow new users via OIDC | `true` |
| `oidc.configurationUrl` | OIDC configuration URL | `""` |
| `oidc.clientId` | Client ID | `""` |
| `oidc.clientSecret` | Client secret | `""` |
| `oidc.userGroup` | Required user group | `""` |
| `oidc.adminGroup` | Admin group | `""` |
| `oidc.autoRedirect` | Redirect to IdP on login | `false` |
| `oidc.providerName` | Provider name on login button | `OAuth` |
| `oidc.rememberMe` | Extend session ("Remember Me") | `false` |
| `oidc.signingAlgorithm` | ID token signing algorithm | `RS256` |
| `oidc.userClaim` | Claim to identify user | `email` |
| `oidc.nameClaim` | Claim for user name | `name` |
| `oidc.groupsClaim` | Claim for groups | `groups` |
| `oidc.existingSecret` | Existing secret name | `""` |
| `oidc.clientIdKey` | Key for client ID in secret | `oidc-client-id` |
| `oidc.clientSecretKey` | Key for client secret in secret | `oidc-client-secret` |
### OpenAI Configuration
| Name | Description | Default |
|------|-------------|---------|
| `openai.enabled` | Enable OpenAI | `false` |
| `openai.baseUrl` | OpenAI API base URL | `""` |
| `openai.apiKey` | OpenAI API key | `""` |
| `openai.model` | Model to use | `gpt-4o` |
| `openai.enableImageServices` | Enable image services | `true` |
| `openai.workers` | Workers per request | `2` |
| `openai.sendDatabaseData` | Send DB data for accuracy | `true` |
| `openai.requestTimeout` | Request timeout (seconds) | `60` |
| `openai.existingSecret` | Existing secret name | `""` |
| `openai.apiKeyKey` | Key for API key in secret | `openai-api-key` |
### Email Configuration
| Name | Description | Default |
|------|-------------|---------|
| `email.enabled` | Enable SMTP email | `false` |
| `email.host` | SMTP host | `""` |
| `email.port` | SMTP port | `587` |
| `email.fromName` | From name | `Mealie` |
| `email.authStrategy` | Auth strategy (`TLS`, `SSL`, `NONE`) | `TLS` |
| `email.fromEmail` | From email address | `""` |
| `email.user` | SMTP username | `""` |
| `email.password` | SMTP password | `""` |
| `email.existingSecret` | Existing secret for SMTP | `""` |
| `email.userKey` | Key for username in secret | `smtp-user` |
| `email.passwordKey` | Key for password in secret | `smtp-password` |
### TLS Configuration
| Name | Description | Default |
|------|-------------|---------|
| `tls.enabled` | Enable TLS | `false` |
| `tls.certificatePath` | TLS certificate path | `""` |
| `tls.privateKeyPath` | TLS private key path | `""` |
| `tls.existingSecret` | Existing secret with TLS certs | `""` |
| `tls.certificateKey` | Key for certificate in secret | `tls.crt` |
| `tls.privateKeyKey` | Key for private key in secret | `tls.key` |
### Theme Configuration
| Name | Description | Default |
|------|-------------|---------|
| `theme.light.primary` | Light theme primary color | `#E58325` |
| `theme.light.accent` | Light theme accent color | `#007A99` |
| `theme.light.secondary` | Light theme secondary color | `#973542` |
| `theme.light.success` | Light theme success color | `#43A047` |
| `theme.light.info` | Light theme info color | `#1976D2` |
| `theme.light.warning` | Light theme warning color | `#FF6D00` |
| `theme.light.error` | Light theme error color | `#EF5350` |
| `theme.dark.primary` | Dark theme primary color | `#E58325` |
| `theme.dark.accent` | Dark theme accent color | `#007A99` |
| `theme.dark.secondary` | Dark theme secondary color | `#973542` |
| `theme.dark.success` | Dark theme success color | `#43A047` |
| `theme.dark.info` | Dark theme info color | `#1976D2` |
| `theme.dark.warning` | Dark theme warning color | `#FF6D00` |
| `theme.dark.error` | Dark theme error color | `#EF5350` |
### Resource Parameters
| Name | Description | Default |
|------|-------------|---------|
| `resources` | Resource limits and requests | `{}` |
### Health Check Parameters
| Name | Description | Default |
|------|-------------|---------|
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.path` | Liveness probe path | `/` |
| `probes.liveness.initialDelaySeconds` | Liveness initial delay | `60` |
| `probes.liveness.periodSeconds` | Liveness period | `30` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.path` | Readiness probe path | `/` |
| `probes.readiness.initialDelaySeconds` | Readiness initial delay | `30` |
| `probes.readiness.periodSeconds` | Readiness period | `10` |
### Autoscaling Parameters
| Name | Description | Default |
|------|-------------|---------|
| `autoscaling.enabled` | Enable HPA | `false` |
| `autoscaling.minReplicas` | Min replicas | `1` |
| `autoscaling.maxReplicas` | Max replicas | `3` |
| `autoscaling.targetCPUUtilizationPercentage` | Target CPU | `80` |
| `autoscaling.targetMemoryUtilizationPercentage` | Target memory | `80` |
## Troubleshooting
Common issues and solutions:
1. **Database connection issues**: Verify database credentials and network connectivity
2. **Persistence issues**: Check StorageClass and PVC configuration
3. **Authentication problems**: Verify LDAP/OIDC configuration and network access
4. **Performance issues**: Adjust resource limits and consider using external database
For more detailed troubleshooting, check the application logs:
- **Database connection issues**: Verify credentials and network connectivity
- **Persistence issues**: Check StorageClass and PVC configuration
- **Authentication problems**: Verify LDAP/OIDC configuration and network access
- **Performance issues**: Adjust resource limits and consider using an external database
```bash
kubectl logs -f deployment/mealie
kubectl describe pod -l app.kubernetes.io/name=mealie
```
## Links
- [Mealie GitHub](https://github.com/mealie-recipes/mealie)
- [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/mealie)

View File

@ -2,8 +2,8 @@ apiVersion: v2
name: norish
description: Norish helm chart for Kubernetes - A recipe management and meal planning application
type: application
version: 0.0.3
appVersion: "v0.13.6-beta"
version: 0.0.5
appVersion: "v0.15.4-beta"
maintainers:
- name: Richard Tomik
email: no@m.com

View File

@ -1,86 +1,39 @@
cl# Norish Helm Chart
# Norish Helm Chart
A Helm chart for deploying [Norish](https://github.com/norishapp/norish), a recipe management and meal planning application, on Kubernetes.
## Introduction
This chart bootstraps a Norish deployment on a Kubernetes cluster using the Helm package manager.
This chart deploys Norish on a Kubernetes cluster. Norish requires an external PostgreSQL database, a Redis server, and includes a Chrome headless sidecar for recipe parsing. It supports multiple authentication methods including password auth, OIDC, GitHub OAuth, and Google OAuth.
**IMPORTANT: This chart requires a central PostgreSQL database.** You must have a PostgreSQL server available before deploying this chart. The chart does not include a PostgreSQL deployment.
**Note:** This chart includes a Chrome headless sidecar container that is required for recipe parsing and scraping functionality. Chrome requires elevated security privileges (`SYS_ADMIN` capability) and additional resources (recommend 256Mi-512Mi memory).
Source code: https://github.com/rtomik/helm-charts/tree/main/charts/norish
## Prerequisites
- Kubernetes 1.19+
- Helm 3.0+
- **PostgreSQL database server** (required)
- PV provisioner support in the underlying infrastructure (if persistence is enabled)
- **PostgreSQL database** (required)
- **Redis server** (required)
- PV provisioner support (if persistence is enabled)
## Installing the Chart
To install the chart with the release name `norish`:
```bash
$ helm repo add helm-charts https://rtomik.github.io/helm-charts
$ helm install norish helm-charts/norish
helm repo add rtomik https://rtomik.github.io/helm-charts
helm install norish rtomik/norish
```
The command deploys Norish on the Kubernetes cluster with default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation.
## Uninstalling the Chart
To uninstall/delete the `norish` deployment:
```bash
helm uninstall norish
```
This command removes all the Kubernetes components associated with the chart and deletes the release.
## Configuration Examples
## Configuration
### Required Configuration
Before deploying, you must configure:
1. **PostgreSQL Database** (REQUIRED): A central PostgreSQL database must be available
- Configure `database.host` to point to your PostgreSQL server
- Ensure the database exists before deployment
- Set appropriate credentials
2. **Master Key**: A 32-byte base64-encoded encryption key
```bash
# Generate a master key
openssl rand -base64 32
```
3. **Application URL**: Set `config.authUrl` to match your ingress hostname
### Authentication Configuration
**Authentication providers are now optional!** You can deploy Norish in two ways:
**Option 1: Password Authentication (Simple Setup)**
- No external authentication provider required
- Users can register and log in with email/password
- Perfect for self-hosted, single-tenant deployments
- Enabled automatically when no OAuth/OIDC provider is configured
**Option 2: OAuth/OIDC Provider (Enterprise Setup)**
- Configure ONE of the following:
- OIDC/OAuth2
- GitHub OAuth
- Google OAuth
- Recommended for multi-user environments
- Can be combined with password authentication via `config.passwordAuthEnabled`
### Example: Minimal Installation (Password Authentication)
This is the simplest setup using built-in password authentication:
### Minimal Installation (Password Authentication)
```yaml
# values.yaml
database:
host: "postgresql.default.svc.cluster.local"
port: 5432
@ -88,11 +41,15 @@ database:
username: norish
password: "secure-password"
redis:
host: "redis.default.svc.cluster.local"
port: 6379
database: 0
config:
authUrl: "https://norish.example.com"
masterKey:
value: "<your-32-byte-base64-key>"
# passwordAuthEnabled defaults to true when no OAuth/OIDC is configured
value: "<your-32-byte-base64-key>" # Generate: openssl rand -base64 32
ingress:
enabled: true
@ -106,355 +63,53 @@ ingress:
- norish.example.com
```
Install with:
```bash
$ helm repo add helm-charts https://rtomik.github.io/helm-charts
$ helm install norish helm-charts/norish -f values.yaml
```
### Example: Installation with OIDC
For enterprise deployments with an external identity provider:
### Production with Existing Secrets
```yaml
# values.yaml
database:
host: "postgresql.default.svc.cluster.local"
port: 5432
name: norish
username: norish
password: "secure-password"
config:
authUrl: "https://norish.example.com"
masterKey:
value: "<your-32-byte-base64-key>"
# Optional: Allow both OIDC and password authentication
passwordAuthEnabled: "true"
auth:
oidc:
enabled: true
name: "MyAuth"
issuer: "https://auth.example.com"
clientId: "<your-client-id>"
clientSecret: "<your-client-secret>"
ingress:
enabled: true
hosts:
- host: norish.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- norish.example.com
```
Install with:
```bash
$ helm repo add helm-charts https://rtomik.github.io/helm-charts
$ helm install norish helm-charts/norish -f values.yaml
```
### Example: Using Existing Secrets
For production deployments, store sensitive data in Kubernetes secrets:
```yaml
# values.yaml
database:
host: "postgresql.default.svc.cluster.local"
existingSecret: "norish-db-secret"
usernameKey: "username"
passwordKey: "password"
redis:
existingSecret: "norish-redis-secret"
urlKey: "redis-url"
config:
authUrl: "https://norish.example.com"
masterKey:
existingSecret: "norish-master-key"
secretKey: "master-key"
auth:
oidc:
enabled: true
name: "MyAuth"
issuer: "https://auth.example.com"
existingSecret: "norish-oidc-secret"
clientIdKey: "client-id"
clientSecretKey: "client-secret"
```
Create the secrets:
Create the required secrets:
```bash
# Database credentials
kubectl create secret generic norish-db-secret \
--from-literal=username="norish" \
--from-literal=password="secure-db-password"
# Master encryption key
kubectl create secret generic norish-redis-secret \
--from-literal=redis-url="redis://username:password@redis.default.svc.cluster.local:6379/0"
kubectl create secret generic norish-master-key \
--from-literal=master-key="$(openssl rand -base64 32)"
# OIDC credentials
kubectl create secret generic norish-oidc-secret \
--from-literal=client-id="<your-client-id>" \
--from-literal=client-secret="<your-client-secret>"
```
### Example: Using Existing PVC
If you want to use an existing PersistentVolumeClaim for uploads storage:
```yaml
# values.yaml
persistence:
enabled: true
existingClaim: "my-existing-pvc"
```
This is useful when:
- You want to reuse storage from a previous installation
- You have pre-provisioned PVCs with specific configurations
- You're managing PVCs separately from the Helm chart
### Optional Configuration
Version v0.13.6-beta introduces additional optional configuration options:
```yaml
config:
# Log level configuration
logLevel: "info" # Options: trace, debug, info, warn, error, fatal
# Additional trusted origins (useful when behind a proxy or using multiple domains)
trustedOrigins: "http://192.168.1.100:3000,https://norish.example.com"
# Enable/disable password authentication
# Defaults to true when no OAuth/OIDC is configured, false otherwise
# Set to "true" to enable password auth alongside OAuth/OIDC
passwordAuthEnabled: "true"
auth:
oidc:
enabled: true
name: "MyAuth"
issuer: "https://auth.example.com"
# Optional: Custom well-known configuration URL
# By default derived from issuer
wellKnown: "https://auth.example.com/.well-known/openid-configuration"
clientId: "<your-client-id>"
clientSecret: "<your-client-secret>"
```
### Customizing Chrome Headless Resources
Chrome headless is required but you can customize its resource limits:
```yaml
chrome:
enabled: true # Must be true for v0.13.6+
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
```
### Setting Up PostgreSQL Database
You need to create the database before deploying this chart:
```sql
-- Connect to your PostgreSQL server
CREATE DATABASE norish;
CREATE USER norish WITH ENCRYPTED PASSWORD 'secure-password';
GRANT ALL PRIVILEGES ON DATABASE norish TO norish;
```
Or if using a centralized PostgreSQL Helm chart or service, ensure the database is created and accessible from your Kubernetes cluster.
## Parameters
### Global Parameters
| Name | Description | Default |
|------|-------------|---------|
| `nameOverride` | Override the chart name | `""` |
| `fullnameOverride` | Override the full resource names | `""` |
### Image Parameters
| Name | Description | Default |
|------|-------------|---------|
| `image.repository` | Norish image repository | `norishapp/norish` |
| `image.tag` | Norish image tag | `v0.13.6-beta` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `imagePullSecrets` | Image pull secrets | `[]` |
### Deployment Parameters
| Name | Description | Default |
|------|-------------|---------|
| `replicaCount` | Number of replicas | `1` |
| `revisionHistoryLimit` | Number of old ReplicaSets to retain | `3` |
### Service Parameters
| Name | Description | Default |
|------|-------------|---------|
| `service.type` | Kubernetes service type | `ClusterIP` |
| `service.port` | Service port | `3000` |
| `service.annotations` | Service annotations | `{}` |
### Ingress Parameters
| Name | Description | Default |
|------|-------------|---------|
| `ingress.enabled` | Enable ingress | `false` |
| `ingress.className` | Ingress class name | `""` |
| `ingress.annotations` | Ingress annotations | `{"traefik.ingress.kubernetes.io/router.entrypoints": "websecure"}` |
| `ingress.hosts` | Ingress hosts configuration | See values.yaml |
| `ingress.tls` | Ingress TLS configuration | See values.yaml |
### Persistence Parameters
| Name | Description | Default |
|------|-------------|---------|
| `persistence.enabled` | Enable persistent storage | `true` |
| `persistence.existingClaim` | Use an existing PVC instead of creating a new one | `""` |
| `persistence.storageClass` | Storage class name | `""` |
| `persistence.accessMode` | Access mode | `ReadWriteOnce` |
| `persistence.size` | Storage size | `5Gi` |
| `persistence.annotations` | PVC annotations | `{}` |
### Application Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.authUrl` | Application URL (required) | `"http://norish.domain.com"` |
| `config.masterKey.value` | Master encryption key | `""` |
| `config.masterKey.existingSecret` | Use existing secret for master key | `""` |
| `config.logLevel` | Log level: trace, debug, info, warn, error, fatal | `""` |
| `config.trustedOrigins` | Additional trusted origins (comma-separated) | `""` |
| `config.passwordAuthEnabled` | Enable/disable password authentication (defaults to true when no OAuth/OIDC configured) | `""` |
| `config.auth.oidc.enabled` | Enable OIDC authentication | `false` |
| `config.auth.oidc.name` | OIDC provider name | `"MyAuth"` |
| `config.auth.oidc.issuer` | OIDC issuer URL | `""` |
| `config.auth.oidc.wellKnown` | OIDC well-known configuration URL (optional) | `""` |
| `config.auth.oidc.clientId` | OIDC client ID | `""` |
| `config.auth.oidc.clientSecret` | OIDC client secret | `""` |
| `config.auth.github.enabled` | Enable GitHub OAuth | `false` |
| `config.auth.github.clientId` | GitHub client ID | `""` |
| `config.auth.github.clientSecret` | GitHub client secret | `""` |
| `config.auth.google.enabled` | Enable Google OAuth | `false` |
| `config.auth.google.clientId` | Google client ID | `""` |
| `config.auth.google.clientSecret` | Google client secret | `""` |
### Database Parameters (REQUIRED)
| Name | Description | Default |
|------|-------------|---------|
| `database.host` | PostgreSQL database host (required) | `""` |
| `database.port` | PostgreSQL database port | `5432` |
| `database.name` | PostgreSQL database name | `norish` |
| `database.username` | PostgreSQL username | `postgres` |
| `database.password` | PostgreSQL password | `""` |
| `database.existingSecret` | Use existing secret for database credentials | `""` |
| `database.usernameKey` | Key in secret for username | `"username"` |
| `database.passwordKey` | Key in secret for password | `"password"` |
| `database.databaseKey` | Key in secret for database name | `"database"` |
| `database.hostKey` | Key in secret for host | `"host"` |
### Chrome Headless Parameters (REQUIRED)
| Name | Description | Default |
|------|-------------|---------|
| `chrome.enabled` | Enable Chrome headless sidecar (required for v0.13.6+) | `true` |
| `chrome.image.repository` | Chrome headless image repository | `zenika/alpine-chrome` |
| `chrome.image.tag` | Chrome headless image tag | `latest` |
| `chrome.image.pullPolicy` | Chrome image pull policy | `IfNotPresent` |
| `chrome.port` | Chrome remote debugging port | `3000` |
| `chrome.resources` | Chrome container resource limits/requests | `{}` |
### Security Parameters
| Name | Description | Default |
|------|-------------|---------|
| `podSecurityContext.runAsNonRoot` | Run as non-root user | `true` |
| `podSecurityContext.runAsUser` | User ID to run as | `1000` |
| `podSecurityContext.fsGroup` | Group ID for filesystem | `1000` |
### Resource Parameters
| Name | Description | Default |
|------|-------------|---------|
| `resources` | CPU/Memory resource requests/limits | `{}` |
### Health Check Parameters
| Name | Description | Default |
|------|-------------|---------|
| `probes.startup.enabled` | Enable startup probe | `true` |
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
## What's New in v0.13.6-beta
This version introduces several improvements and new features:
**UI/UX Improvements:**
- Ability to change prompts used in Settings → Admin
- Improved transcriber logic
- Double tapping/clicking planned recipes now opens the recipe page
- Small icon that opens the original recipe page
- Add recipes button now opens a dropdown instead of instantly redirecting to manual creation
**New Features:**
- Support for trusting additional origins using `TRUSTED_ORIGINS` environment variable (comma-separated)
- Customizable password authentication via `PASSWORD_AUTH_ENABLED` flag
- Configurable log level via `NEXT_PUBLIC_LOG_LEVEL`
**Bug Fixes:**
- User menu remaining open when clicking import
- Text truncation no longer uses the tailwind truncate class in the calendar
- Comma decimals being parsed as nothing (e.g., 2,5 ended up as 25)
- Unicode character handling
**Breaking Changes:**
- Chrome headless is now mandatory for improved parsing functionality
## Authentication Setup
Norish v0.13.6-beta and later support multiple authentication methods:
### Password Authentication (Default)
When no external authentication provider is configured, Norish automatically enables password-based authentication. Users can:
- Register new accounts with email and password
- Log in using their credentials
- Manage their account through the web interface
This is the simplest setup and perfect for:
- Self-hosted, single-user or family deployments
- Testing and development environments
- Scenarios where external OAuth providers are not needed
### External Authentication Providers (Optional)
For enterprise or multi-tenant deployments, you can configure external authentication providers. After configuring a provider, you can manage additional authentication methods through the Settings → Admin interface.
### OIDC/OAuth2
### OIDC Authentication
```yaml
config:
auth:
oidc:
enabled: true
name: "Authentik" # Display name
name: "Authentik"
issuer: "https://auth.example.com/application/o/norish/"
clientId: "<your-client-id>"
clientSecret: "<your-client-secret>"
# Optional: allow password auth alongside OIDC
passwordAuthEnabled: "true"
```
### GitHub OAuth
@ -485,60 +140,216 @@ config:
clientSecret: "<your-google-client-secret>"
```
### Using Existing PVC
```yaml
persistence:
enabled: true
existingClaim: "my-existing-pvc"
```
## Parameters
### Global Parameters
| Name | Description | Default |
|------|-------------|---------|
| `nameOverride` | Override the release name | `""` |
| `fullnameOverride` | Fully override the release name | `""` |
### Image Parameters
| Name | Description | Default |
|------|-------------|---------|
| `image.repository` | Norish image repository | `norishapp/norish` |
| `image.tag` | Image tag | `v0.15.4-beta` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `imagePullSecrets` | Image pull secrets | `[]` |
### Deployment Parameters
| Name | Description | Default |
|------|-------------|---------|
| `replicaCount` | Number of replicas | `1` |
| `revisionHistoryLimit` | Revisions to retain | `3` |
| `podSecurityContext.runAsNonRoot` | Run as non-root | `true` |
| `podSecurityContext.runAsUser` | User ID | `1000` |
| `podSecurityContext.fsGroup` | Filesystem group ID | `1000` |
| `nodeSelector` | Node selector | `{}` |
| `tolerations` | Tolerations | `[]` |
| `affinity` | Affinity rules | `{}` |
| `podAnnotations` | Pod annotations | `{}` |
### Service Parameters
| Name | Description | Default |
|------|-------------|---------|
| `service.type` | Service type | `ClusterIP` |
| `service.port` | Service port | `3000` |
| `service.annotations` | Service annotations | `{}` |
### Ingress Parameters
| Name | Description | Default |
|------|-------------|---------|
| `ingress.enabled` | Enable ingress | `false` |
| `ingress.className` | Ingress class name | `""` |
| `ingress.annotations` | Ingress annotations | See values.yaml |
| `ingress.hosts` | Ingress hosts | See values.yaml |
| `ingress.tls` | TLS configuration | See values.yaml |
### Persistence Parameters
| Name | Description | Default |
|------|-------------|---------|
| `persistence.enabled` | Enable persistence | `true` |
| `persistence.existingClaim` | Use an existing PVC | `""` |
| `persistence.storageClass` | Storage class | `""` |
| `persistence.accessMode` | Access mode | `ReadWriteOnce` |
| `persistence.size` | PVC size | `5Gi` |
| `persistence.annotations` | PVC annotations | `{}` |
### Database Configuration (Required)
| Name | Description | Default |
|------|-------------|---------|
| `database.host` | PostgreSQL host | `""` |
| `database.port` | PostgreSQL port | `5432` |
| `database.name` | Database name | `norish` |
| `database.username` | Username | `postgres` |
| `database.password` | Password | `""` |
| `database.existingSecret` | Existing secret name | `""` |
| `database.usernameKey` | Key for username in secret | `username` |
| `database.passwordKey` | Key for password in secret | `password` |
| `database.databaseKey` | Key for database name in secret | `database` |
| `database.hostKey` | Key for host in secret | `""` |
### Redis Configuration (Required)
| Name | Description | Default |
|------|-------------|---------|
| `redis.host` | Redis host | `""` |
| `redis.port` | Redis port | `6379` |
| `redis.database` | Redis database number | `0` |
| `redis.username` | Redis username (6.0+) | `""` |
| `redis.password` | Redis password | `""` |
| `redis.existingSecret` | Existing secret name | `""` |
| `redis.urlKey` | Key for full Redis URL in secret | `redis-url` |
| `redis.passwordKey` | Key for password in secret | `password` |
### Application Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.authUrl` | Application URL (must match ingress) | `http://norish.domain.com` |
| `config.logLevel` | Log level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`) | `""` |
| `config.trustedOrigins` | Additional trusted origins (comma-separated) | `""` |
| `config.passwordAuthEnabled` | Enable/disable password auth | `""` |
| `config.extraEnv` | Extra environment variables | `[]` |
### Master Key Configuration (Required)
| Name | Description | Default |
|------|-------------|---------|
| `config.masterKey.value` | 32-byte base64 encryption key | `""` |
| `config.masterKey.existingSecret` | Existing secret name | `""` |
| `config.masterKey.secretKey` | Key in secret | `master-key` |
Generate with: `openssl rand -base64 32`
### OIDC Authentication
| Name | Description | Default |
|------|-------------|---------|
| `config.auth.oidc.enabled` | Enable OIDC | `false` |
| `config.auth.oidc.name` | Provider display name | `MyAuth` |
| `config.auth.oidc.issuer` | OIDC issuer URL | `""` |
| `config.auth.oidc.clientId` | Client ID | `""` |
| `config.auth.oidc.clientSecret` | Client secret | `""` |
| `config.auth.oidc.wellKnown` | Well-known URL (optional) | `""` |
| `config.auth.oidc.existingSecret` | Existing secret name | `""` |
| `config.auth.oidc.clientIdKey` | Key for client ID in secret | `oidc-client-id` |
| `config.auth.oidc.clientSecretKey` | Key for client secret in secret | `oidc-client-secret` |
### GitHub OAuth
| Name | Description | Default |
|------|-------------|---------|
| `config.auth.github.enabled` | Enable GitHub OAuth | `false` |
| `config.auth.github.clientId` | Client ID | `""` |
| `config.auth.github.clientSecret` | Client secret | `""` |
| `config.auth.github.existingSecret` | Existing secret name | `""` |
| `config.auth.github.clientIdKey` | Key for client ID in secret | `github-client-id` |
| `config.auth.github.clientSecretKey` | Key for client secret in secret | `github-client-secret` |
### Google OAuth
| Name | Description | Default |
|------|-------------|---------|
| `config.auth.google.enabled` | Enable Google OAuth | `false` |
| `config.auth.google.clientId` | Client ID | `""` |
| `config.auth.google.clientSecret` | Client secret | `""` |
| `config.auth.google.existingSecret` | Existing secret name | `""` |
| `config.auth.google.clientIdKey` | Key for client ID in secret | `google-client-id` |
| `config.auth.google.clientSecretKey` | Key for client secret in secret | `google-client-secret` |
### Chrome Headless Parameters
| Name | Description | Default |
|------|-------------|---------|
| `chrome.enabled` | Enable Chrome sidecar | `true` |
| `chrome.image.repository` | Chrome image repository | `zenika/alpine-chrome` |
| `chrome.image.tag` | Chrome image tag | `latest` |
| `chrome.image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `chrome.port` | Chrome debugging port | `9222` |
| `chrome.securityContext` | Chrome security context (requires root + SYS_ADMIN) | See values.yaml |
| `chrome.resources` | Chrome resource limits | `{}` |
### Resource Parameters
| Name | Description | Default |
|------|-------------|---------|
| `resources` | Resource limits and requests | `{}` |
### Health Check Parameters
| Name | Description | Default |
|------|-------------|---------|
| `probes.startup.enabled` | Enable startup probe | `true` |
| `probes.startup.initialDelaySeconds` | Startup initial delay | `10` |
| `probes.startup.periodSeconds` | Startup period | `10` |
| `probes.startup.failureThreshold` | Startup failure threshold | `30` |
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.initialDelaySeconds` | Liveness initial delay | `30` |
| `probes.liveness.periodSeconds` | Liveness period | `10` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.initialDelaySeconds` | Readiness initial delay | `5` |
| `probes.readiness.periodSeconds` | Readiness period | `5` |
## Upgrading
### From v0.13.x to v0.14.x
**Breaking change**: Redis is now required. Configure Redis before upgrading (see [Redis Configuration](#redis-configuration-required)).
### From v0.14.x to v0.15.x
No configuration changes required. Redis, PostgreSQL, and Chrome headless are already configured. Back up your database before upgrading as a precaution.
## Troubleshooting
### Check Pod Status
- **Master Key Not Set**: Generate with `openssl rand -base64 32`
- **Login Failures**: Password auth is enabled by default when no OAuth/OIDC is configured. Verify callback URLs match your ingress hostname.
- **Database Connection Failed**: Verify host, credentials, and that the database exists.
- **Chrome Headless Issues**: Chrome requires `SYS_ADMIN` capability and 256Mi-512Mi memory. Check logs with `kubectl logs -l app.kubernetes.io/name=norish -c chrome-headless`
- **Recipe Parsing Failures**: Ensure Chrome is running. `CHROME_WS_ENDPOINT` is automatically configured by the chart.
```bash
kubectl get pods -l app.kubernetes.io/name=norish
kubectl logs -l app.kubernetes.io/name=norish
```
### Check Database Connection
## Links
```bash
# Test connection from app pod
kubectl exec -it deployment/norish -- sh
nc -zv <your-postgres-host> 5432
```
### Common Issues
1. **Master Key Not Set**: Ensure you've generated and configured a master key
2. **Cannot Log In**:
- Password authentication is enabled by default when no OAuth/OIDC is configured
- If you configured an external provider, ensure the client ID/secret are correct
- Check the callback URL matches your ingress hostname
3. **Database Connection Failed**:
- Verify database host is correct and accessible from the cluster
- Check database credentials
- Ensure the database exists
- Verify network policies allow connections to the database
4. **Application Not Accessible**: Verify ingress configuration and DNS records
5. **Chrome Headless Issues**:
- Chrome requires `SYS_ADMIN` capability for proper operation
- If pod fails to start, check if your cluster's security policies allow the required capabilities
- Chrome container may require additional memory (256Mi-512Mi recommended)
- Check Chrome container logs: `kubectl logs -l app.kubernetes.io/name=norish -c chrome-headless`
6. **Recipe Parsing Failures**:
- Ensure Chrome headless is running: `kubectl get pods -l app.kubernetes.io/name=norish`
- Verify `CHROME_WS_ENDPOINT` is set correctly (automatically configured by the chart)
- Check if Chrome is accessible from the Norish container
## Upgrading
To upgrade the chart:
```bash
$ helm upgrade norish helm-charts/norish -f values.yaml
```
## Support
- Norish Repository: https://github.com/norishapp/norish
- Chart Repository: https://github.com/rtomik/helm-charts
- Issue Tracker: https://github.com/rtomik/helm-charts/issues
## License
This Helm chart is provided as-is under the same license as the Norish application.
- [Norish GitHub](https://github.com/norishapp/norish)
- [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/norish)

View File

@ -55,3 +55,43 @@ Database connection URL
{{- $database := .Values.database.name }}
{{- printf "postgres://%s:%s@%s:%d/%s" $username $password $host (int $port) $database }}
{{- end }}
{{/*
Redis URL (for non-authenticated Redis)
Constructs the Redis URL without authentication.
Format: redis://host:port/database
*/}}
{{- define "norish.redis.url.noauth" -}}
{{- $host := .Values.redis.host }}
{{- $port := .Values.redis.port }}
{{- $database := .Values.redis.database | toString }}
{{- printf "redis://%s:%d/%s" $host (int $port) $database }}
{{- end }}
{{/*
Check if Redis authentication is configured
Returns true if either existingSecret or password is set
*/}}
{{- define "norish.redis.hasAuth" -}}
{{- if or .Values.redis.existingSecret .Values.redis.password }}
{{- "true" }}
{{- end }}
{{- end }}
{{/*
Redis URL with authentication (for secret generation)
Constructs the Redis URL with password interpolation for use in secrets.
Format: redis://[username]:[password]@host:port/database
*/}}
{{- define "norish.redis.url.withPassword" -}}
{{- $host := .Values.redis.host }}
{{- $port := .Values.redis.port }}
{{- $database := .Values.redis.database | toString }}
{{- $username := .Values.redis.username | default "" }}
{{- $password := .Values.redis.password | default "" }}
{{- if $username }}
{{- printf "redis://%s:%s@%s:%d/%s" $username $password $host (int $port) $database }}
{{- else }}
{{- printf "redis://:%s@%s:%d/%s" $password $host (int $port) $database }}
{{- end }}
{{- end }}

View File

@ -145,6 +145,16 @@ spec:
name: {{ include "norish.fullname" . }}-secret
key: master-key
{{- end }}
- name: REDIS_URL
valueFrom:
secretKeyRef:
{{- if .Values.redis.existingSecret }}
name: {{ .Values.redis.existingSecret }}
key: {{ .Values.redis.urlKey | default "redis-url" }}
{{- else }}
name: {{ include "norish.fullname" . }}-secret
key: redis-url
{{- end }}
{{- if .Values.config.auth.oidc.enabled }}
- name: OIDC_NAME
value: {{ .Values.config.auth.oidc.name | quote }}

View File

@ -12,6 +12,13 @@ stringData:
{{- if not .Values.database.existingSecret }}
database-url: {{ include "norish.databaseUrl" . | quote }}
{{- end }}
{{- if not .Values.redis.existingSecret }}
{{- if .Values.redis.password }}
redis-url: {{ include "norish.redis.url.withPassword" . | quote }}
{{- else }}
redis-url: {{ include "norish.redis.url.noauth" . | quote }}
{{- end }}
{{- end }}
{{- if .Values.config.auth.oidc.enabled }}
{{- if not .Values.config.auth.oidc.existingSecret }}
oidc-client-id: {{ .Values.config.auth.oidc.clientId | quote }}

View File

@ -5,7 +5,7 @@ fullnameOverride: ""
## Image settings
image:
repository: norishapp/norish
tag: "v0.13.6-beta"
tag: "v0.16.2-beta"
pullPolicy: IfNotPresent
imagePullSecrets: []
@ -211,6 +211,24 @@ database:
databaseKey: "database" # Key in the secret for database name (optional)
hostKey: "" # Key in the secret for database host (optional)
## External Redis configuration (REQUIRED for v0.14.0+)
## Redis is required for job queues and background tasks starting from v0.14.0-beta
redis:
# Redis connection details
host: "" # Required: Redis server hostname
port: 6379
database: 0
# Authentication (leave empty if Redis has no auth)
username: "" # Optional: Redis username (Redis 6.0+)
password: "" # Redis password (leave empty if no auth)
# Use existing secret for Redis credentials (recommended for production)
# NOTE: When using existingSecret, the secret MUST contain a key with the full Redis URL
# Format: redis://[username]:[password]@host:port/database
existingSecret: "" # Name of existing Kubernetes secret
urlKey: "redis-url" # Key in existingSecret containing the full Redis URL
passwordKey: "password" # Key in existingSecret for password (for compatibility)
## Chrome Headless configuration (REQUIRED)
## Required for improved recipe parsing and scraping
chrome:

View File

@ -2,11 +2,11 @@ apiVersion: v2
name: paperless-ngx
description: Paperless-ngx helm chart for Kubernetes
type: application
version: 0.0.2
appVersion: "latest"
version: 0.0.5
appVersion: "2.20.3"
maintainers:
- name: Richard Tomik
email: no@m.com
email: richard.tomik@proton.me
keywords:
- productivity
- document-management

View File

@ -1,172 +1,59 @@
# Paperless-ngx Helm Chart
A Helm chart for deploying Paperless-ngx document management system on Kubernetes.
A Helm chart for deploying [Paperless-ngx](https://github.com/paperless-ngx/paperless-ngx), a document management system with OCR, on Kubernetes.
## Introduction
This chart deploys [Paperless-ngx](https://github.com/paperless-ngx/paperless-ngx) on a Kubernetes cluster using the Helm package manager.
This chart deploys Paperless-ngx on a Kubernetes cluster. Paperless-ngx is a community-supported document scanner: scan, index, and archive all your physical documents. It requires external PostgreSQL and Redis services.
Paperless-ngx is a community-supported supercharged version of paperless: scan, index and archive all your physical documents.
Source code can be found here:
- https://github.com/rtomik/helm-charts/tree/main/charts/paperless-ngx
Source code: https://github.com/rtomik/helm-charts/tree/main/charts/paperless-ngx
## Prerequisites
- Kubernetes 1.19+
- Helm 3.0+
- PV provisioner support in the underlying infrastructure
- **External PostgreSQL database** (required)
- **External Redis server** (required)
## External Dependencies
This chart requires external PostgreSQL and Redis services. It does not deploy these dependencies to avoid resource conflicts on centralized servers.
### PostgreSQL Setup
Paperless-ngx requires PostgreSQL 11+ as its database backend. Ensure you have:
- A PostgreSQL database created for Paperless-ngx
- Database credentials configured in values.yaml or via secrets
### Redis Setup
Redis is required for background task processing. Ensure you have:
- A Redis server accessible from the cluster
- Connection details configured in values.yaml
- Optional: Redis authentication credentials (username/password)
- Optional: Redis key prefix for sharing one Redis server among multiple Paperless instances
The chart supports all Redis authentication methods:
- No authentication: `redis://host:port/database`
- Password only (requirepass): `redis://:password@host:port/database`
- Username and password (Redis 6.0+ ACL): `redis://username:password@host:port/database`
- **External PostgreSQL database** (PostgreSQL 11+ required)
- **External Redis server**
- PV provisioner support
## Installing the Chart
To install the chart with the release name `paperless-ngx`:
```bash
$ helm repo add paperless-chart https://rtomik.github.io/helm-charts
$ helm install paperless-ngx paperless-chart/paperless-ngx
helm repo add rtomik https://rtomik.github.io/helm-charts
helm install paperless-ngx rtomik/paperless-ngx
```
Or install directly from this repository:
## Uninstalling the Chart
```bash
$ git clone https://github.com/rtomik/helm-charts.git
$ cd helm-charts/charts/paperless-ngx
$ helm install paperless-ngx .
helm uninstall paperless-ngx
```
> **Tip**: List all releases using `helm list`
## Configuration
The following table lists the configurable parameters and their default values.
### Global Parameters
| Name | Description | Value |
|------------------------|-------------------------------------------------------------------------------------|-------|
| `nameOverride` | String to partially override the release name | `""` |
| `fullnameOverride` | String to fully override the release name | `""` |
### Image Parameters
| Name | Description | Value |
|-------------------------|--------------------------------------------------------------------------------------|--------------------|
| `image.repository` | Paperless-ngx image repository | `ghcr.io/paperless-ngx/paperless-ngx` |
| `image.tag` | Paperless-ngx image tag | `latest` |
| `image.pullPolicy` | Paperless-ngx image pull policy | `IfNotPresent` |
### External Dependencies
| Name | Description | Value |
|----------------------------------------|--------------------------------------------------------------------|-------------------------------------------|
| `postgresql.external.enabled` | Enable external PostgreSQL configuration | `true` |
| `postgresql.external.host` | External PostgreSQL host | `postgresql.default.svc.cluster.local` |
| `postgresql.external.port` | External PostgreSQL port | `5432` |
| `postgresql.external.database` | External PostgreSQL database name | `paperless` |
| `postgresql.external.username` | External PostgreSQL username | `paperless` |
| `postgresql.external.existingSecret` | Existing secret with PostgreSQL credentials | `""` |
| `postgresql.external.passwordKey` | Key in existing secret for PostgreSQL password | `postgresql-password` |
| `redis.external.enabled` | Enable external Redis configuration | `true` |
| `redis.external.host` | External Redis host | `redis.default.svc.cluster.local` |
| `redis.external.port` | External Redis port | `6379` |
| `redis.external.database` | External Redis database number | `0` |
| `redis.external.username` | Redis username (Redis 6.0+ with ACL) | `""` |
| `redis.external.password` | Redis password (leave empty if no auth required) | `""` |
| `redis.external.existingSecret` | Existing secret with Redis credentials | `""` |
| `redis.external.passwordKey` | Key in existing secret for Redis password | `redis-password` |
| `redis.external.prefix` | Prefix for Redis keys/channels (for multi-instance) | `""` |
### Security Configuration
| Name | Description | Value |
|----------------------------------------|--------------------------------------------------------------------|---------------------|
| `config.secretKey.existingSecret` | Name of existing secret for Django secret key | `""` |
| `config.secretKey.secretKey` | Key in the existing secret for Django secret key | `secret-key` |
| `config.admin.user` | Admin username to create on startup | `""` |
| `config.admin.password` | Admin password (use existingSecret for production) | `""` |
| `config.admin.email` | Admin email address | `root@localhost` |
| `config.admin.existingSecret` | Name of existing secret for admin credentials | `""` |
### Application Configuration
| Name | Description | Value |
|----------------------------------------|--------------------------------------------------------------------|---------------------|
| `config.url` | External URL for Paperless-ngx (e.g., https://paperless.domain.com) | `""` |
| `config.allowedHosts` | Comma-separated list of allowed hosts | `*` |
| `config.timeZone` | Application timezone | `UTC` |
| `config.ocr.language` | OCR language (3-letter code) | `eng` |
| `config.ocr.mode` | OCR mode (skip, redo, force) | `skip` |
| `config.consumer.recursive` | Enable recursive consumption directory watching | `false` |
| `config.consumer.subdirsAsTags` | Use subdirectory names as tags | `false` |
### Persistence Parameters
| Name | Description | Value |
|----------------------------------------|--------------------------------------------------------------------|---------------------|
| `persistence.data.enabled` | Enable persistence for data directory | `true` |
| `persistence.data.size` | Size of data PVC | `1Gi` |
| `persistence.media.enabled` | Enable persistence for media directory | `true` |
| `persistence.media.size` | Size of media PVC | `10Gi` |
| `persistence.consume.enabled` | Enable persistence for consume directory | `true` |
| `persistence.consume.size` | Size of consume PVC | `5Gi` |
| `persistence.export.enabled` | Enable persistence for export directory | `true` |
| `persistence.export.size` | Size of export PVC | `1Gi` |
### Service Parameters
| Name | Description | Value |
|----------------------------|------------------------------------------------------|-------------|
| `service.type` | Kubernetes Service type | `ClusterIP` |
| `service.port` | Service HTTP port | `8000` |
### Ingress Parameters
| Name | Description | Value |
|----------------------------|------------------------------------------------------|----------------------|
| `ingress.enabled` | Enable ingress record generation | `false` |
| `ingress.className` | IngressClass name | `""` |
| `ingress.annotations` | Additional annotations for the Ingress resource | See values.yaml |
| `ingress.hosts` | Array of host and path objects | See values.yaml |
| `ingress.tls` | TLS configuration | See values.yaml |
## Usage Examples
### Basic Installation
**Note**: PVCs are not deleted automatically. To remove them:
```bash
helm install paperless-ngx . \
--set postgresql.external.host=my-postgres.example.com \
--set postgresql.external.password=secretpassword \
--set redis.external.host=my-redis.example.com
kubectl delete pvc -l app.kubernetes.io/instance=paperless-ngx
```
### Production Installation with External Secrets
## Configuration Examples
### Minimal Installation
```yaml
postgresql:
external:
enabled: true
host: "my-postgres.example.com"
password: "secretpassword"
redis:
external:
host: "my-redis.example.com"
```
### Production with Existing Secrets
```yaml
# values-production.yaml
config:
url: "https://paperless.example.com"
allowedHosts: "paperless.example.com"
@ -178,14 +65,12 @@ config:
existingSecret: "paperless-admin-secrets"
postgresql:
# External PostgreSQL connection details
external:
enabled: true
host: "postgres-cluster-pooler.dbs.svc.cluster.local"
port: 5432
database: "paperless"
username: "paperless"
# Use existingSecret for credentials
existingSecret: "paperless-db-credentials"
passwordKey: "password"
@ -194,10 +79,8 @@ redis:
host: "redis.cache.svc.cluster.local"
port: 6379
database: 0
# Use existingSecret for Redis credentials
existingSecret: "paperless-redis-credentials"
passwordKey: "password"
# Optional: Use prefix to share Redis among multiple instances
prefix: "paperless-prod"
ingress:
@ -214,40 +97,17 @@ ingress:
- paperless.example.com
```
```bash
helm install paperless-ngx . -f values-production.yaml
```
### Redis Authentication Examples
#### Redis with Password Only (requirepass)
```bash
helm install paperless-ngx . \
--set redis.external.host=redis.example.com \
--set redis.external.password=myredispassword
```
Or with existing secret:
### Redis with Username and Password (ACL)
```yaml
redis:
external:
host: "redis.example.com"
existingSecret: "redis-auth-secret"
passwordKey: "redis-password"
username: "paperless-user"
password: "myredispassword"
```
#### Redis with Username and Password (Redis 6.0+ ACL)
```bash
helm install paperless-ngx . \
--set redis.external.host=redis.example.com \
--set redis.external.username=paperless-user \
--set redis.external.password=myredispassword
```
#### Multiple Paperless Instances on One Redis Server
### Sharing Redis Among Multiple Instances
Use the `prefix` parameter to avoid key collisions:
@ -267,47 +127,183 @@ redis:
prefix: "paperless-staging"
```
## Security Considerations
### Using Existing PVCs
1. **Use external secrets** for production deployments to store sensitive data like database passwords, Redis passwords, and the Django secret key.
2. **Set a proper PAPERLESS_URL** when exposing the application externally.
3. **Configure ALLOWED_HOSTS** to restrict which hosts can access the application.
4. **Use HTTPS** when exposing the application to the internet.
5. **Secure Redis**: Always use authentication (password or username/password) for Redis in production environments. Use `existingSecret` instead of plain text passwords.
6. **Container Security**: The container runs as root initially to allow s6-overlay to set up the runtime environment, then drops privileges to UID 1000. This is required for the Paperless-ngx Docker image to function properly.
## Volumes and Data
Paperless-ngx uses several directories:
- **Data directory**: Contains the search index, classification model, and SQLite database (if used)
- **Media directory**: Contains all uploaded documents and thumbnails
- **Consume directory**: Drop documents here for automatic processing
- **Export directory**: Used for document exports
All directories can be configured with separate PVCs and storage classes.
## Uninstalling the Chart
To uninstall/delete the `paperless-ngx` deployment:
```bash
helm uninstall paperless-ngx
```yaml
persistence:
data:
enabled: true
existingClaim: "my-existing-data-pvc"
media:
enabled: true
existingClaim: "my-existing-media-pvc"
export:
enabled: true
consume:
enabled: true
```
The command removes all the Kubernetes components associated with the chart and deletes the release.
When `existingClaim` is set, the chart skips PVC creation and `storageClass`/`size` are ignored for that volume.
## Contributing
## Parameters
Please feel free to contribute by opening issues or pull requests at:
https://github.com/rtomik/helm-charts
### Global Parameters
## License
| Name | Description | Default |
|------|-------------|---------|
| `nameOverride` | Override the release name | `""` |
| `fullnameOverride` | Fully override the release name | `""` |
This Helm chart is licensed under the MIT License.
### Image Parameters
| Name | Description | Default |
|------|-------------|---------|
| `image.repository` | Paperless-ngx image repository | `ghcr.io/paperless-ngx/paperless-ngx` |
| `image.tag` | Image tag | `2.20.3` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
### Deployment Parameters
| Name | Description | Default |
|------|-------------|---------|
| `replicaCount` | Number of replicas | `1` |
| `revisionHistoryLimit` | Revisions to retain | `3` |
| `podSecurityContext.runAsNonRoot` | Run as non-root | `false` |
| `podSecurityContext.runAsUser` | User ID | `0` |
| `podSecurityContext.fsGroup` | Filesystem group ID | `1000` |
| `nodeSelector` | Node selector | `{}` |
| `tolerations` | Tolerations | `[]` |
| `affinity` | Affinity rules | `{}` |
### Service Parameters
| Name | Description | Default |
|------|-------------|---------|
| `service.type` | Service type | `ClusterIP` |
| `service.port` | Service port | `8000` |
### Ingress Parameters
| Name | Description | Default |
|------|-------------|---------|
| `ingress.enabled` | Enable ingress | `false` |
| `ingress.className` | Ingress class name | `""` |
| `ingress.annotations` | Ingress annotations | See values.yaml |
| `ingress.hosts` | Ingress hosts | See values.yaml |
| `ingress.tls` | TLS configuration | See values.yaml |
### PostgreSQL Configuration (Required)
| Name | Description | Default |
|------|-------------|---------|
| `postgresql.external.enabled` | Enable external PostgreSQL | `true` |
| `postgresql.external.host` | PostgreSQL host | `postgresql.default.svc.cluster.local` |
| `postgresql.external.port` | PostgreSQL port | `5432` |
| `postgresql.external.database` | Database name | `paperless` |
| `postgresql.external.username` | Username | `paperless` |
| `postgresql.external.password` | Password | `""` |
| `postgresql.external.existingSecret` | Existing secret name | `""` |
| `postgresql.external.passwordKey` | Key for password in secret | `postgresql-password` |
### Redis Configuration (Required)
| Name | Description | Default |
|------|-------------|---------|
| `redis.external.enabled` | Enable external Redis | `true` |
| `redis.external.host` | Redis host | `redis.default.svc.cluster.local` |
| `redis.external.port` | Redis port | `6379` |
| `redis.external.database` | Redis database number | `0` |
| `redis.external.username` | Redis username (6.0+ ACL) | `""` |
| `redis.external.password` | Redis password | `""` |
| `redis.external.existingSecret` | Existing secret name | `""` |
| `redis.external.urlKey` | Key for full Redis URL in secret | `redis-url` |
| `redis.external.passwordKey` | Key for password in secret | `redis-password` |
| `redis.external.prefix` | Key prefix for multi-instance | `""` |
### Application Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.url` | External URL | `""` |
| `config.allowedHosts` | Allowed hosts (comma-separated) | `*` |
| `config.csrfTrustedOrigins` | CSRF trusted origins | `""` |
| `config.timeZone` | Timezone | `UTC` |
| `config.ocr.language` | OCR language (3-letter code) | `eng` |
| `config.ocr.mode` | OCR mode (`skip`, `redo`, `force`) | `skip` |
| `config.consumer.recursive` | Recursive consume directory | `false` |
| `config.consumer.subdirsAsTags` | Use subdirectory names as tags | `false` |
### Security Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.secretKey.existingSecret` | Existing secret for Django secret key | `""` |
| `config.secretKey.secretKey` | Key in secret | `secret-key` |
| `config.admin.user` | Admin username to create on startup | `""` |
| `config.admin.password` | Admin password | `""` |
| `config.admin.email` | Admin email | `root@localhost` |
| `config.admin.existingSecret` | Existing secret for admin credentials | `""` |
### Persistence Parameters
| Name | Description | Default |
|------|-------------|---------|
| `persistence.data.enabled` | Enable data PVC | `true` |
| `persistence.data.existingClaim` | Existing data PVC | `""` |
| `persistence.data.size` | Data PVC size | `1Gi` |
| `persistence.media.enabled` | Enable media PVC | `true` |
| `persistence.media.existingClaim` | Existing media PVC | `""` |
| `persistence.media.size` | Media PVC size | `10Gi` |
| `persistence.consume.enabled` | Enable consume PVC | `true` |
| `persistence.consume.existingClaim` | Existing consume PVC | `""` |
| `persistence.consume.size` | Consume PVC size | `5Gi` |
| `persistence.export.enabled` | Enable export PVC | `true` |
| `persistence.export.existingClaim` | Existing export PVC | `""` |
| `persistence.export.size` | Export PVC size | `1Gi` |
### Resource Parameters
| Name | Description | Default |
|------|-------------|---------|
| `resources` | Resource limits and requests | `{}` |
### Health Check Parameters
| Name | Description | Default |
|------|-------------|---------|
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.path` | Liveness probe path | `/` |
| `probes.liveness.initialDelaySeconds` | Liveness initial delay | `60` |
| `probes.liveness.periodSeconds` | Liveness period | `10` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.path` | Readiness probe path | `/` |
| `probes.readiness.initialDelaySeconds` | Readiness initial delay | `30` |
| `probes.readiness.periodSeconds` | Readiness period | `5` |
### Autoscaling Parameters
| Name | Description | Default |
|------|-------------|---------|
| `autoscaling.enabled` | Enable HPA | `false` |
| `autoscaling.minReplicas` | Min replicas | `1` |
| `autoscaling.maxReplicas` | Max replicas | `3` |
| `autoscaling.targetCPUUtilizationPercentage` | Target CPU | `80` |
| `autoscaling.targetMemoryUtilizationPercentage` | Target memory | `80` |
## Troubleshooting
- **Database Connection**: Verify PostgreSQL credentials and that the database exists
- **Redis Connection**: Ensure Redis is accessible; use `prefix` if sharing Redis between instances
- **Allowed Hosts Error**: Set `config.allowedHosts` to your domain when exposed externally
- **Container Security**: The container runs as root initially for s6-overlay setup, then drops to UID 1000. This is required by the Paperless-ngx image.
```bash
kubectl logs -f deployment/paperless-ngx
kubectl describe pod -l app.kubernetes.io/name=paperless-ngx
```
## Links
- [Paperless-ngx GitHub](https://github.com/paperless-ngx/paperless-ngx)
- [Paperless-ngx Documentation](https://docs.paperless-ngx.com/)
- [Paperless-ngx GitHub Repository](https://github.com/paperless-ngx/paperless-ngx)
- [Docker Hub](https://hub.docker.com/r/ghcr.io/paperless-ngx/paperless-ngx)
- [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/paperless-ngx)

View File

@ -89,21 +89,42 @@ Redis port
{{- end }}
{{/*
Redis URL
Constructs the Redis URL with optional authentication.
Redis URL (for non-authenticated Redis)
Constructs the Redis URL without authentication.
Format: redis://host:port/database
*/}}
{{- define "paperless-ngx.redis.url.noauth" -}}
{{- $host := include "paperless-ngx.redis.host" . }}
{{- $port := include "paperless-ngx.redis.port" . }}
{{- $database := .Values.redis.external.database | toString }}
{{- printf "redis://%s:%s/%s" $host $port $database }}
{{- end }}
{{/*
Check if Redis authentication is configured
Returns true if either existingSecret or password is set
*/}}
{{- define "paperless-ngx.redis.hasAuth" -}}
{{- if or .Values.redis.external.existingSecret .Values.redis.external.password }}
{{- "true" }}
{{- end }}
{{- end }}
{{/*
Redis URL with authentication (for secret generation)
Constructs the Redis URL with password interpolation for use in secrets.
This uses the actual password value when building the secret.
Format: redis://[username]:[password]@host:port/database
*/}}
{{- define "paperless-ngx.redis.url" -}}
{{- define "paperless-ngx.redis.url.withPassword" -}}
{{- $host := include "paperless-ngx.redis.host" . }}
{{- $port := include "paperless-ngx.redis.port" . }}
{{- $database := .Values.redis.external.database | toString }}
{{- $username := .Values.redis.external.username | default "" }}
{{- $password := .Values.redis.external.password | default "" }}
{{- if and $username $password }}
{{- if $username }}
{{- printf "redis://%s:%s@%s:%s/%s" $username $password $host $port $database }}
{{- else if $password }}
{{- printf "redis://:%s@%s:%s/%s" $password $host $port $database }}
{{- else }}
{{- printf "redis://%s:%s/%s" $host $port $database }}
{{- printf "redis://:%s@%s:%s/%s" $password $host $port $database }}
{{- end }}
{{- end }}

View File

@ -67,12 +67,23 @@ spec:
{{- end }}
env:
# Required services
{{- if include "paperless-ngx.redis.hasAuth" . }}
# When Redis has authentication, read the full URL from secret
- name: PAPERLESS_REDIS
value: {{ include "paperless-ngx.redis.url" . | quote }}
valueFrom:
secretKeyRef:
name: {{ .Values.redis.external.existingSecret | default (printf "%s-secrets" (include "paperless-ngx.fullname" .)) }}
key: {{ .Values.redis.external.urlKey | default "redis-url" }}
{{- else }}
# When Redis has no authentication, use the simple URL
- name: PAPERLESS_REDIS
value: {{ include "paperless-ngx.redis.url.noauth" . | quote }}
{{- end }}
{{- if .Values.redis.external.prefix }}
- name: PAPERLESS_REDIS_PREFIX
value: {{ .Values.redis.external.prefix | quote }}
{{- end }}
- name: PAPERLESS_DBHOST
value: {{ include "paperless-ngx.postgresql.host" . | quote }}
- name: PAPERLESS_DBPORT
@ -324,7 +335,7 @@ spec:
{{- if .Values.persistence.data.enabled }}
- name: data
persistentVolumeClaim:
claimName: {{ include "paperless-ngx.fullname" . }}-data
claimName: {{ if .Values.persistence.data.existingClaim }}{{ .Values.persistence.data.existingClaim }}{{ else }}{{ include "paperless-ngx.fullname" . }}-data{{ end }}
{{- else }}
- name: data
emptyDir: {}
@ -332,7 +343,7 @@ spec:
{{- if .Values.persistence.media.enabled }}
- name: media
persistentVolumeClaim:
claimName: {{ include "paperless-ngx.fullname" . }}-media
claimName: {{ if .Values.persistence.media.existingClaim }}{{ .Values.persistence.media.existingClaim }}{{ else }}{{ include "paperless-ngx.fullname" . }}-media{{ end }}
{{- else }}
- name: media
emptyDir: {}
@ -340,7 +351,7 @@ spec:
{{- if .Values.persistence.export.enabled }}
- name: export
persistentVolumeClaim:
claimName: {{ include "paperless-ngx.fullname" . }}-export
claimName: {{ if .Values.persistence.export.existingClaim }}{{ .Values.persistence.export.existingClaim }}{{ else }}{{ include "paperless-ngx.fullname" . }}-export{{ end }}
{{- else }}
- name: export
emptyDir: {}
@ -348,7 +359,7 @@ spec:
{{- if .Values.persistence.consume.enabled }}
- name: consume
persistentVolumeClaim:
claimName: {{ include "paperless-ngx.fullname" . }}-consume
claimName: {{ if .Values.persistence.consume.existingClaim }}{{ .Values.persistence.consume.existingClaim }}{{ else }}{{ include "paperless-ngx.fullname" . }}-consume{{ end }}
{{- else }}
- name: consume
emptyDir: {}

View File

@ -1,4 +1,4 @@
{{- if .Values.persistence.data.enabled }}
{{- if and .Values.persistence.data.enabled (not .Values.persistence.data.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
@ -21,7 +21,7 @@ spec:
---
{{- end }}
{{- if .Values.persistence.media.enabled }}
{{- if and .Values.persistence.media.enabled (not .Values.persistence.media.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
@ -44,7 +44,7 @@ spec:
---
{{- end }}
{{- if .Values.persistence.export.enabled }}
{{- if and .Values.persistence.export.enabled (not .Values.persistence.export.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
@ -67,7 +67,7 @@ spec:
---
{{- end }}
{{- if .Values.persistence.consume.enabled }}
{{- if and .Values.persistence.consume.enabled (not .Values.persistence.consume.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:

View File

@ -32,6 +32,7 @@ data:
{{- end }}
{{- if and .Values.redis.external.password (not .Values.redis.external.existingSecret) }}
{{ .Values.redis.external.passwordKey | default "redis-password" }}: {{ .Values.redis.external.password | b64enc }}
{{ .Values.redis.external.urlKey | default "redis-url" }}: {{ include "paperless-ngx.redis.url.withPassword" . | b64enc }}
{{- end }}
{{- if and .Values.config.admin.user (not .Values.config.admin.existingSecret) }}
{{ .Values.config.admin.userKey | default "admin-user" }}: {{ .Values.config.admin.user | b64enc }}

View File

@ -5,7 +5,7 @@ fullnameOverride: ""
## Image settings
image:
repository: ghcr.io/paperless-ngx/paperless-ngx
tag: "2.18.4"
tag: "2.20.3"
pullPolicy: IfNotPresent
## Deployment settings
@ -65,6 +65,7 @@ persistence:
# Paperless data directory (search index, classification model, etc.)
data:
enabled: true
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 1Gi
@ -72,6 +73,7 @@ persistence:
# Paperless media directory (documents and thumbnails)
media:
enabled: true
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 10Gi
@ -79,6 +81,7 @@ persistence:
# Export directory (for exporting documents)
export:
enabled: true
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 1Gi
@ -86,6 +89,7 @@ persistence:
# Consume directory (for importing documents)
consume:
enabled: true
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 5Gi
@ -161,9 +165,13 @@ redis:
# Authentication (leave empty if Redis has no auth)
username: "" # Optional: Redis username (Redis 6.0+)
# Use existingSecret for credentials if Redis has auth
# NOTE: When using existingSecret, the secret MUST contain a key with the full Redis URL
# Format: redis://[username]:[password]@host:port/database
existingSecret: ""
passwordKey: "redis-password"
urlKey: "redis-url" # Key in existingSecret containing the full Redis URL
passwordKey: "redis-password" # Key in existingSecret for password (for compatibility)
# Or set password directly (leave empty if no auth)
# When using plain password, the full Redis URL will be auto-generated in the secret
password: ""
# Optional: Prefix for Redis keys and channels
# Useful for sharing one Redis server among multiple Paperless instances

View File

@ -1,117 +1,52 @@
# qBittorrent with Gluetun VPN
# qBittorrent VPN Helm Chart
A Helm chart for deploying qBittorrent with a Gluetun VPN sidecar container on Kubernetes.
A Helm chart for deploying [qBittorrent](https://www.qbittorrent.org/) with a [Gluetun](https://github.com/qdm12/gluetun) VPN sidecar on Kubernetes.
## Introduction
This chart deploys [qBittorrent](https://www.qbittorrent.org/) alongside [Gluetun](https://github.com/qdm12/gluetun), a VPN client/tunnel in a container, to ensure all BitTorrent traffic is routed through the VPN. The chart supports all major VPN providers and protocols through Gluetun's comprehensive compatibility.
This chart deploys qBittorrent alongside Gluetun, ensuring all BitTorrent traffic is routed through a VPN. It supports 30+ VPN providers via OpenVPN or WireGuard, includes a kill-switch firewall, and exposes HTTP/Socks proxy services.
Source code can be found here:
- https://github.com/rtomik/helm-charts/tree/main/charts/qbittorrent-vpn
Source code: https://github.com/rtomik/helm-charts/tree/main/charts/qbittorrent-vpn
Note: Currently only tested with NordVPN an OpenVPN configuration.
## Features
- **Multiple VPN Providers**: Support for 30+ VPN providers including NordVPN, ProtonVPN, Private Internet Access, ExpressVPN, Surfshark, Mullvad, and more
- **Protocol Support**: Use OpenVPN or WireGuard based on your provider's capabilities
- **Server Selection**: Choose servers by country, city, or specific hostnames with optional randomization
- **Security**: Proper container security settings to ensure traffic only flows through the VPN
- **Health Monitoring**: Integrated health checks for both qBittorrent and the VPN connection
- **Persistence**: Separate volume storage for configuration and downloads
- **Web UI**: Access qBittorrent via web interface with optional ingress support
- **Proxy Services**: HTTP and Shadowsocks proxies for additional devices to use the VPN tunnel
**Note**: Currently only tested with NordVPN and OpenVPN configuration.
## Prerequisites
- Kubernetes 1.19+
- Helm 3.2.0+
- PV provisioner support in the cluster
- A valid subscription to a VPN service
- A valid VPN subscription
## Installation
### Add the Repository
## Installing the Chart
```bash
helm repo add rtomik-charts https://rtomik.github.io/helm-charts
helm repo update
helm repo add rtomik https://rtomik.github.io/helm-charts
helm install qbittorrent-vpn rtomik/qbittorrent-vpn
```
### Create a Secret for VPN Credentials
For better security, store your VPN credentials in a Kubernetes secret:
## Uninstalling the Chart
```bash
# For OpenVPN authentication
kubectl create secret generic vpn-credentials \
--namespace default \
--from-literal=username='your-vpn-username' \
--from-literal=password='your-vpn-password'
# For WireGuard authentication (if using WireGuard)
kubectl create secret generic wireguard-keys \
--namespace default \
--from-literal=private_key='your-wireguard-private-key'
helm uninstall qbittorrent-vpn
```
Then reference this secret in your values:
```yaml
gluetun:
credentials:
create: false
existingSecret: "vpn-credentials"
usernameKey: "username"
passwordKey: "password"
```
### Install the Chart
```bash
# Option 1: Installation with custom values file (recommended)
helm install qbittorrent-vpn rtomik-charts/qbittorrent-vpn -f values.yaml -n media
# Option 2: Installation with inline parameter overrides
helm install qbittorrent-vpn rtomik-charts/qbittorrent-vpn -n media \
--set gluetun.vpn.provider=nordvpn \
--set gluetun.vpn.serverCountries=Germany \
--set-string gluetun.credentials.existingSecret=vpn-credentials
```
## Uninstallation
```bash
helm uninstall qbittorrent-vpn -n media
```
Note: This will not delete Persistent Volume Claims. To delete them:
**Note**: PVCs are not deleted automatically. To remove them:
```bash
kubectl delete pvc -l app.kubernetes.io/instance=qbittorrent-vpn
```
## Configuration
## Configuration Examples
### Key Parameters
### NordVPN with Existing Secret
| Parameter | Description | Default |
|---------------------------------------|-------------------------------------------------------|----------------------------|
| `qbittorrent.image.repository` | qBittorrent image repository | `linuxserver/qbittorrent` |
| `qbittorrent.image.tag` | qBittorrent image tag | `latest` |
| `gluetun.image.repository` | Gluetun image repository | `qmcgaw/gluetun` |
| `gluetun.image.tag` | Gluetun image tag | `v3.40.0` |
| `gluetun.vpn.provider` | VPN provider name | `nordvpn` |
| `gluetun.vpn.type` | VPN protocol (`openvpn` or `wireguard`) | `openvpn` |
| `gluetun.vpn.serverCountries` | Countries to connect to (comma-separated) | `Germany` |
| `persistence.config.size` | Size of PVC for qBittorrent config | `2Gi` |
| `persistence.downloads.size` | Size of PVC for downloads | `100Gi` |
| `ingress.enabled` | Enable ingress controller resource | `true` |
| `ingress.hosts[0].host` | Hostname for the ingress | `qbittorrent.domain.com` |
First, create a secret with your VPN credentials:
For a complete list of parameters, see the [values.yaml](values.yaml) file.
### Example: Using with NordVPN
```bash
kubectl create secret generic vpn-credentials \
--from-literal=username='your-vpn-username' \
--from-literal=password='your-vpn-password'
```
```yaml
gluetun:
@ -120,14 +55,26 @@ gluetun:
type: "openvpn"
serverCountries: "United States"
openvpn:
NORDVPN_CATEGORY: "P2P" # For torrent-optimized servers
NORDVPN_CATEGORY: "P2P"
credentials:
create: true
username: "your-nordvpn-username"
password: "your-nordvpn-password"
create: false
existingSecret: "vpn-credentials"
usernameKey: "username"
passwordKey: "password"
ingress:
enabled: true
hosts:
- host: qbittorrent.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- qbittorrent.example.com
```
### Example: Using with ProtonVPN
### ProtonVPN
```yaml
gluetun:
@ -136,15 +83,15 @@ gluetun:
type: "openvpn"
serverCountries: "Switzerland"
openvpn:
PROTONVPN_TIER: "2" # 0 is free, 2 is paid (Plus/Visionary)
SERVER_FEATURES: "p2p" # For torrent support
PROTONVPN_TIER: "2"
SERVER_FEATURES: "p2p"
credentials:
create: true
username: "protonvpn-username"
password: "protonvpn-password"
```
### Example: Using with Private Internet Access
### Private Internet Access with Port Forwarding
```yaml
gluetun:
@ -157,40 +104,11 @@ gluetun:
username: "pia-username"
password: "pia-password"
settings:
VPN_PORT_FORWARDING: "on" # PIA supports port forwarding
VPN_PORT_FORWARDING: "on"
STATUS_FILE: "/tmp/gluetun-status.json"
```
## VPN Provider Support
This chart supports all VPN providers compatible with Gluetun, including:
- AirVPN
- Cyberghost
- ExpressVPN
- FastestVPN
- HideMyAss
- IPVanish
- IVPN
- Mullvad
- NordVPN
- Perfect Privacy
- Private Internet Access (PIA)
- PrivateVPN
- ProtonVPN
- PureVPN
- Surfshark
- TorGuard
- VyprVPN
- WeVPN
- Windscribe
For the complete list and provider-specific options, see the [Gluetun Providers Documentation](https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers).
## Additional Features
### Accessing the HTTP Proxy
Gluetun provides an HTTP proxy on port 8888 that can be used by other applications to route traffic through the VPN. To expose this proxy:
### Proxy Services
```yaml
service:
@ -200,33 +118,9 @@ service:
socksPort: 8388
```
### Firewall Settings
### Custom Sidecar (NATMap)
By default, the chart enables the Gluetun firewall to prevent leaks if the VPN connection drops. You can customize this:
```yaml
gluetun:
settings:
FIREWALL: "on"
FIREWALL_OUTBOUND_SUBNETS: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
```
### Port Forwarding
For VPN providers that support port forwarding (like PIA):
```yaml
gluetun:
settings:
VPN_PORT_FORWARDING: "on"
STATUS_FILE: "/tmp/gluetun-status.json"
```
### Custom Sidecar Containers
The chart supports adding custom sidecar containers to the pod. This is useful for adding additional functionality like port forwarding management (NATMap), monitoring, or other helper containers.
Sidecars are specified using the standard Kubernetes container specification:
Sidecars can access shared volumes: `config`, `downloads`, and `gluetun-config`.
```yaml
sidecars:
@ -246,67 +140,164 @@ sidecars:
subPath: natmap
```
**Common Use Cases:**
## Parameters
1. **NATMap**: Automatically update port forwarding configurations
2. **Monitoring**: Add monitoring agents or exporters
3. **Custom Scripts**: Run periodic maintenance or update tasks
### qBittorrent Parameters
**Sharing Volumes:**
| Name | Description | Default |
|------|-------------|---------|
| `qbittorrent.image.repository` | qBittorrent image repository | `linuxserver/qbittorrent` |
| `qbittorrent.image.tag` | qBittorrent image tag | `5.1.0` |
| `qbittorrent.image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `qbittorrent.bittorrentPort` | BitTorrent traffic port | `6881` |
| `qbittorrent.service.port` | Web UI port | `8080` |
Sidecars can access the same volumes as the main containers:
- `config`: qBittorrent configuration volume
- `downloads`: Downloads volume
- `gluetun-config`: Gluetun configuration volume (if enabled)
### Gluetun VPN Parameters
For the full Kubernetes container specification reference, see the [Kubernetes documentation](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#container-v1-core).
| Name | Description | Default |
|------|-------------|---------|
| `gluetun.enabled` | Enable Gluetun VPN sidecar | `true` |
| `gluetun.image.repository` | Gluetun image repository | `qmcgaw/gluetun` |
| `gluetun.image.tag` | Gluetun image tag | `v3.40.0` |
| `gluetun.vpn.provider` | VPN provider name | `nordvpn` |
| `gluetun.vpn.type` | VPN protocol (`openvpn` or `wireguard`) | `openvpn` |
| `gluetun.vpn.serverCountries` | Countries to connect (comma-separated) | `Netherlands` |
| `gluetun.vpn.serverCities` | Cities to connect (optional) | `""` |
| `gluetun.vpn.serverNames` | Specific server names (optional) | `""` |
| `gluetun.vpn.randomize` | Randomize server selection | `true` |
### VPN Credentials
| Name | Description | Default |
|------|-------------|---------|
| `gluetun.credentials.create` | Create credentials secret | `true` |
| `gluetun.credentials.username` | VPN username (if creating secret) | `""` |
| `gluetun.credentials.password` | VPN password (if creating secret) | `""` |
| `gluetun.credentials.existingSecret` | Existing secret name | `""` |
| `gluetun.credentials.usernameKey` | Key for username in secret | `username` |
| `gluetun.credentials.passwordKey` | Key for password in secret | `password` |
### Gluetun Settings
| Name | Description | Default |
|------|-------------|---------|
| `gluetun.settings.FIREWALL` | Enable firewall | `on` |
| `gluetun.settings.FIREWALL_OUTBOUND_SUBNETS` | Allowed outbound subnets | `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` |
| `gluetun.settings.FIREWALL_INPUT_PORTS` | Ports allowed through firewall | `8080` |
| `gluetun.settings.FIREWALL_DEBUG` | Enable firewall debug | `on` |
| `gluetun.settings.VPN_PORT_FORWARDING` | Enable port forwarding | `off` |
| `gluetun.settings.DNS_ADDRESS` | DNS server address | `1.1.1.1` |
| `gluetun.resources.limits.cpu` | CPU limit | `300m` |
| `gluetun.resources.limits.memory` | Memory limit | `256Mi` |
### WireGuard Configuration (when using WireGuard)
| Name | Description | Default |
|------|-------------|---------|
| `gluetun.vpn.wireguard.privateKey` | WireGuard private key | `""` |
| `gluetun.vpn.wireguard.privateKeyExistingSecret` | Existing secret with private key | `""` |
| `gluetun.vpn.wireguard.addresses` | WireGuard addresses | `""` |
| `gluetun.vpn.wireguard.endpointIP` | Server endpoint IP (optional) | `""` |
| `gluetun.vpn.wireguard.endpointPort` | Server endpoint port (optional) | `""` |
### Deployment Parameters
| Name | Description | Default |
|------|-------------|---------|
| `replicaCount` | Number of replicas | `1` |
| `revisionHistoryLimit` | Revisions to retain | `3` |
| `podSecurityContext.runAsNonRoot` | Run as non-root | `false` |
| `podSecurityContext.runAsUser` | User ID | `0` |
| `podSecurityContext.fsGroup` | Filesystem group ID | `0` |
| `nodeSelector` | Node selector | `{}` |
| `tolerations` | Tolerations | `[]` |
| `affinity` | Affinity rules | `{}` |
### Service Parameters
| Name | Description | Default |
|------|-------------|---------|
| `service.type` | Service type | `ClusterIP` |
| `service.port` | Service port | `8080` |
| `service.proxies.enabled` | Enable HTTP/Socks proxy services | `false` |
| `service.proxies.httpPort` | HTTP proxy port | `8888` |
| `service.proxies.socksPort` | Socks proxy port | `8388` |
### Ingress Parameters
| Name | Description | Default |
|------|-------------|---------|
| `ingress.enabled` | Enable ingress | `false` |
| `ingress.className` | Ingress class name | `""` |
| `ingress.annotations` | Ingress annotations | `[]` |
| `ingress.hosts` | Ingress hosts | See values.yaml |
| `ingress.tls` | TLS configuration | See values.yaml |
### Persistence Parameters
| Name | Description | Default |
|------|-------------|---------|
| `qbittorrent.persistence.config.enabled` | Enable config PVC | `true` |
| `qbittorrent.persistence.config.existingClaim` | Existing config PVC | `""` |
| `qbittorrent.persistence.config.size` | Config PVC size | `2Gi` |
| `qbittorrent.persistence.downloads.enabled` | Enable downloads PVC | `true` |
| `qbittorrent.persistence.downloads.existingClaim` | Existing downloads PVC | `""` |
| `qbittorrent.persistence.downloads.size` | Downloads PVC size | `2Gi` |
| `gluetun.persistence.enabled` | Enable Gluetun config PVC | `true` |
| `gluetun.persistence.size` | Gluetun config PVC size | `100Mi` |
### Sidecar Parameters
| Name | Description | Default |
|------|-------------|---------|
| `sidecars` | Additional sidecar containers | `[]` |
### Health Check Parameters
| Name | Description | Default |
|------|-------------|---------|
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.path` | Liveness probe path | `/` |
| `probes.liveness.periodSeconds` | Liveness period | `30` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.path` | Readiness probe path | `/` |
| `probes.readiness.periodSeconds` | Readiness period | `10` |
## Supported VPN Providers
AirVPN, Cyberghost, ExpressVPN, FastestVPN, HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Private Internet Access, PrivateVPN, ProtonVPN, PureVPN, Surfshark, TorGuard, VyprVPN, WeVPN, Windscribe, and more.
See the [Gluetun Providers Documentation](https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers) for the full list and provider-specific options.
## Troubleshooting
### VPN Connection Issues
### VPN Not Connecting
If the VPN isn't connecting properly:
```bash
kubectl logs deployment/qbittorrent-vpn -c gluetun
kubectl describe secret vpn-credentials
```
1. Check the Gluetun logs:
```bash
kubectl logs deployment/qbittorrent-vpn -c gluetun
```
Enable debug logging for more detail:
2. Verify your credentials are correct:
```bash
kubectl describe secret vpn-credentials
```
3. Try setting the log level to debug for more detailed information:
```yaml
gluetun:
```yaml
gluetun:
extraEnv:
- name: LOG_LEVEL
value: "debug"
```
```
### qBittorrent Can't Create Directories
### Directory Creation Errors
If you see errors like "Could not create required directory":
Ensure the init container is enabled and `fsGroup` is set in `podSecurityContext`.
1. Make sure the init container is enabled and properly configured
2. Ensure proper `fsGroup` is set in the `podSecurityContext`
3. Check that the persistence volume allows the correct permissions
### Firewall / Network Issues
### Firewall/Security Issues
Gluetun requires `privileged: true` and `NET_ADMIN` capability. Verify `/dev/net/tun` is mounted correctly.
If you encounter iptables or network issues:
## Links
1. Ensure the Gluetun container has `privileged: true`
2. Verify the `NET_ADMIN` capability is added
3. Check that the `/dev/net/tun` device is correctly mounted
## License
This chart is licensed under the MIT License.
## Acknowledgements
- [Gluetun](https://github.com/qdm12/gluetun) by [qdm12](https://github.com/qdm12)
- [LinuxServer.io](https://linuxserver.io/) for the qBittorrent container
- The qBittorrent team for the excellent torrent client
- [qBittorrent](https://www.qbittorrent.org/)
- [Gluetun GitHub](https://github.com/qdm12/gluetun)
- [Gluetun Provider Setup](https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers)
- [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/qbittorrent-vpn)

View File

@ -1,140 +1,43 @@
# Recipya Helm Chart
A Helm chart for deploying [Recipya](https://github.com/reaper47/recipya) on Kubernetes.
[Source Code](https://github.com/rtomik/helm-charts/tree/main/charts%2Frecipya)
A Helm chart for deploying [Recipya](https://github.com/reaper47/recipya), a recipe management application, on Kubernetes.
## Introduction
This chart deploys Recipya recipe manager on a Kubernetes cluster using the Helm package manager.
This chart deploys Recipya on a Kubernetes cluster using the Helm package manager. Recipya includes optimized Traefik ingress configuration with Content Security Policy support and sticky session handling for authentication.
Source code can be found here:
- https://github.com/rtomik/helm-charts/tree/main/charts/recipya
Source code: https://github.com/rtomik/helm-charts/tree/main/charts/recipya
## Prerequisites
- Kubernetes 1.19+
- Helm 3.2.0+
- PV provisioner support in the underlying infrastructure (if persistence is needed)
- PV provisioner support (if persistence is needed)
## Installing the Chart
To install the chart with the release name `recipya`:
```bash
helm repo add recipya-chart https://rtomik.github.io/helm-charts
helm install recipya recipya-chart/recipya -n recipya
helm repo add rtomik https://rtomik.github.io/helm-charts
helm install recipya rtomik/recipya
```
The command deploys Recipya on the Kubernetes cluster in the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation.
## Uninstalling the Chart
To uninstall/delete the `recipya` deployment:
```bash
helm uninstall recipya -n recipya
helm uninstall recipya
```
## Important Configuration Notes
## Configuration Examples
### Server URL
### Minimal Installation
When deploying with an ingress, it's **critical** to set `config.server.url` to match your ingress URL (including https if you're using TLS). This ensures that redirects after login work correctly:
> **Important**: Set `config.server.url` to match your ingress URL including the scheme. This is required for post-login redirects to work correctly.
```yaml
config:
server:
url: "https://your-recipya-domain.com"
```
url: "https://recipya.example.com"
### Ingress Configuration
This chart includes optimized ingress configurations for Traefik, with support for WebSockets and proper security headers. If you're using a different ingress controller, you may need to adjust annotations accordingly.
## Parameters
### Global parameters
| Name | Description | Value |
|--------------------------|--------------------------------------|-----------------|
| `image.repository` | Recipya image repository | `reaper99/recipya` |
| `image.tag` | Recipya image tag | `v1.2.2` |
| `image.pullPolicy` | Recipya image pull policy | `IfNotPresent` |
| `replicaCount` | Number of Recipya replicas | `1` |
| `revisionHistoryLimit` | Number of revisions to keep | `3` |
### Security parameters
| Name | Description | Value |
|-----------------------------------------|--------------------------------------------------|-----------|
| `podSecurityContext.fsGroup` | Group ID for the Recipya container | `1000` |
| `containerSecurityContext` | Security context for the container | `{}` |
### Recipya configuration parameters
| Name | Description | Value |
|-----------------------------------------|-------------------------------------------------------|---------------------|
| `config.server.port` | Server port | `8078` |
| `config.server.autologin` | Whether to login automatically | `false` |
| `config.server.is_demo` | Whether the app is a demo version | `false` |
| `config.server.is_prod` | Whether the app is in production | `false` |
| `config.server.no_signups` | Whether to disable user account registrations | `false` |
| `config.server.url` | Base URL for the application | `http://0.0.0.0` |
| `config.email.address` | The email address for SendGrid | `""` |
| `config.email.sendgrid` | SendGrid API key | `""` |
| `config.documentIntelligence.endpoint` | Azure Document Intelligence endpoint | `""` |
| `config.documentIntelligence.key` | Azure Document Intelligence key | `""` |
### Service parameters
| Name | Description | Value |
|--------------------------|--------------------------------------------------|-------------|
| `service.type` | Recipya service type | `ClusterIP` |
| `service.port` | Recipya service port | `8078` |
### Ingress parameters
| Name | Description | Value |
|-------------------------------|--------------------------------------------------|------------------------|
| `ingress.enabled` | Enable ingress controller resource | `false` |
| `ingress.className` | IngressClass that will be used | `"traefik"` |
| `ingress.annotations` | Additional ingress annotations | See values.yaml |
| `ingress.hosts[0].host` | Default host for the ingress resource | `chart-example.local` |
| `ingress.tls` | TLS configuration | `[]` |
### Persistence parameters
| Name | Description | Value |
|--------------------------------------|------------------------------------------|------------------|
| `persistence.enabled` | Enable persistence using PVC | `true` |
| `persistence.accessMode` | PVC Access Mode | `ReadWriteOnce` |
| `persistence.size` | PVC Storage Request | `1Gi` |
| `persistence.storageClass` | Storage class of backing PVC | `""` |
### Resource parameters
| Name | Description | Value |
|-------------------------------|------------------------------------------|-----------|
| `resources.limits.cpu` | CPU limit | `500m` |
| `resources.limits.memory` | Memory limit | `512Mi` |
| `resources.requests.cpu` | CPU request | `100m` |
| `resources.requests.memory` | Memory request | `128Mi` |
### Probe parameters
| Name | Description | Value |
|--------------------------------------|--------------------------------------------|-----------|
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.path` | Path for liveness probe | `/` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.path` | Path for readiness probe | `/` |
## Traefik Ingress Configuration
The chart includes specially configured middlewares for Traefik to ensure proper functioning of Recipya:
```yaml
ingress:
enabled: true
className: "traefik"
@ -153,34 +56,16 @@ ingress:
- recipya.example.com
```
This configuration includes:
1. Custom Content Security Policy allowing essential scripts from unpkg.com
2. Sticky sessions for maintaining authentication
3. Proper headers for proxy operation
## Content Security Policy Configuration
The chart includes a custom middleware that configures the proper Content Security Policy for Recipya. This is particularly important as the application requires access to external scripts from unpkg.com:
### With SendGrid Email
```yaml
contentSecurityPolicy: >-
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data: https://unpkg.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
font-src 'self' data:;
connect-src 'self' ws: wss: *;
worker-src 'self' blob:;
frame-src 'self';
media-src 'self' blob:;
object-src 'none';
form-action 'self';
config:
email:
address: "your-email@example.com"
sendgrid: "SG.your-sendgrid-api-key"
```
## Using Existing Secrets
If you want to use existing secrets for sensitive data:
### With SendGrid and Azure Document Intelligence via Existing Secrets
```yaml
config:
@ -194,6 +79,127 @@ config:
keyKey: "di_key"
```
## Configuration
## Parameters
See the [Recipya documentation](https://recipes.musicavis.ca/docs/installation/docker/#environment-variables) for details on all available configuration options.
### Global Parameters
| Name | Description | Default |
|------|-------------|---------|
| `nameOverride` | Override the release name | `""` |
| `fullnameOverride` | Fully override the release name | `""` |
| `replicaCount` | Number of replicas | `1` |
| `revisionHistoryLimit` | Revisions to retain | `3` |
### Image Parameters
| Name | Description | Default |
|------|-------------|---------|
| `image.repository` | Recipya image repository | `reaper99/recipya` |
| `image.tag` | Image tag | `v1.2.2` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `imagePullSecrets` | Image pull secrets | `[]` |
### Pod Security Parameters
| Name | Description | Default |
|------|-------------|---------|
| `podSecurityContext.fsGroup` | Filesystem group ID | `1000` |
| `containerSecurityContext` | Container security context | `{}` |
### Application Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.server.port` | Server port | `8078` |
| `config.server.url` | Base URL (must match ingress) | `http://0.0.0.0` |
| `config.server.autologin` | Auto-login | `false` |
| `config.server.is_demo` | Demo mode | `false` |
| `config.server.is_prod` | Production mode | `true` |
| `config.server.no_signups` | Disable user registration | `false` |
| `config.email.address` | SendGrid email address | `""` |
| `config.email.sendgrid` | SendGrid API key | `""` |
| `config.email.existingSecret` | Existing secret for email | `""` |
| `config.email.addressKey` | Key for email address in secret | `email` |
| `config.email.sendgridKey` | Key for SendGrid key in secret | `sendgrid` |
| `config.documentIntelligence.endpoint` | Azure Document Intelligence endpoint | `""` |
| `config.documentIntelligence.key` | Azure Document Intelligence key | `""` |
| `config.documentIntelligence.existingSecret` | Existing secret for Azure DI | `""` |
| `config.documentIntelligence.endpointKey` | Key for endpoint in secret | `di_endpoint` |
| `config.documentIntelligence.keyKey` | Key for API key in secret | `di_key` |
### Service Parameters
| Name | Description | Default |
|------|-------------|---------|
| `service.type` | Service type | `ClusterIP` |
| `service.port` | Service port | `8078` |
### Ingress Parameters
| Name | Description | Default |
|------|-------------|---------|
| `ingress.enabled` | Enable ingress | `false` |
| `ingress.className` | Ingress class name | `""` |
| `ingress.annotations` | Ingress annotations | See values.yaml |
| `ingress.hosts` | Ingress hosts | See values.yaml |
| `ingress.tls` | TLS configuration | `[]` |
### Persistence Parameters
| Name | Description | Default |
|------|-------------|---------|
| `persistence.enabled` | Enable persistence | `false` |
| `persistence.storageClass` | Storage class | `""` |
| `persistence.accessMode` | Access mode | `ReadWriteOnce` |
| `persistence.size` | PVC size | `5Gi` |
| `persistence.annotations` | PVC annotations | `{}` |
### Resource Parameters
| Name | Description | Default |
|------|-------------|---------|
| `resources` | Resource limits and requests | `{}` |
### Health Check Parameters
| Name | Description | Default |
|------|-------------|---------|
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.path` | Liveness probe path | `/` |
| `probes.liveness.initialDelaySeconds` | Liveness initial delay | `30` |
| `probes.liveness.periodSeconds` | Liveness period | `10` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.path` | Readiness probe path | `/` |
| `probes.readiness.initialDelaySeconds` | Readiness initial delay | `30` |
| `probes.readiness.periodSeconds` | Readiness period | `10` |
## Troubleshooting
### Post-Login Redirect Fails
Ensure `config.server.url` matches your ingress URL exactly, including the scheme (`https://`).
### Content Security Policy Errors
The chart includes a Traefik middleware with a CSP policy allowing scripts from `unpkg.com`. If using a different ingress controller, configure an equivalent CSP policy:
```
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data: https://unpkg.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
connect-src 'self' ws: wss: *;
```
### Debugging
```bash
kubectl logs deployment/recipya -f
kubectl describe pod -l app.kubernetes.io/name=recipya
```
## Links
- [Recipya GitHub](https://github.com/reaper47/recipya)
- [Recipya Documentation](https://recipes.musicavis.ca/docs/installation/docker/#environment-variables)
- [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/recipya)

View File

@ -1,30 +1,42 @@
# Tandoor Recipes Helm Chart
A Helm chart for deploying [Tandoor Recipes](https://github.com/TandoorRecipes/recipes) on Kubernetes.
A Helm chart for deploying [Tandoor Recipes](https://github.com/TandoorRecipes/recipes), a recipe management application, on Kubernetes.
Tandoor is a recipe management application that allows you to manage your recipes, plan meals, and create shopping lists.
## Introduction
Source code can be found here:
- https://github.com/rtomik/helm-charts/tree/main/charts/tandoor
This chart deploys Tandoor Recipes on a Kubernetes cluster. Tandoor supports PostgreSQL databases, LDAP/OIDC authentication, S3 object storage, email notifications, AI features, and Food Data Central API integration for nutrition data.
Source code: https://github.com/rtomik/helm-charts/tree/main/charts/tandoor
## Prerequisites
- Kubernetes 1.19+
- Helm 3.0+
- PV provisioner support in the underlying infrastructure
- **External PostgreSQL database** (required - this chart does NOT include PostgreSQL)
- **External PostgreSQL database** (required — this chart does not include PostgreSQL)
- PV provisioner support
## Installing the Chart
```bash
helm repo add rtomik https://rtomik.github.io/helm-charts
helm repo update
helm install tandoor rtomik/tandoor -f values.yaml
helm install tandoor rtomik/tandoor
```
## Usage Examples
## Uninstalling the Chart
### Minimal Configuration
```bash
helm uninstall tandoor
```
**Note**: PVCs are not deleted automatically. To remove them:
```bash
kubectl delete pvc -l app.kubernetes.io/name=tandoor
```
## Configuration Examples
### Minimal Installation
```yaml
postgresql:
@ -38,7 +50,7 @@ config:
value: "your-secret-key-at-least-50-characters-long-for-security-purposes"
```
### Production Configuration
### Production with Existing Secrets
```yaml
postgresql:
@ -52,20 +64,10 @@ config:
secretKey:
existingSecret: "tandoor-app-secret"
secretKey: "secret-key"
allowedHosts: "tandoor.example.com"
csrfTrustedOrigins: "https://tandoor.example.com"
timezone: "Europe/Berlin"
# Optional: OpenID Connect with Authentik
# oidc:
# enabled: true
# providerId: "authentik"
# providerName: "Authentik"
# clientId: "your-client-id"
# clientSecret: "your-client-secret"
# serverUrl: "https://authentik.company/application/o/tandoor/.well-known/openid-configuration"
ingress:
enabled: true
className: "nginx"
@ -84,7 +86,6 @@ ingress:
persistence:
staticfiles:
enabled: true
# existingClaim: "my-existing-pvc"
storageClass: "longhorn"
size: 2Gi
mediafiles:
@ -101,323 +102,349 @@ resources:
memory: 256Mi
```
### OIDC with Authentik
## Configuration
```yaml
config:
oidc:
enabled: true
providerId: "authentik"
providerName: "Authentik"
clientId: "your-client-id"
clientSecret: "your-client-secret"
serverUrl: "https://authentik.company/application/o/tandoor/.well-known/openid-configuration"
```
All configuration options are based on the official Tandoor documentation:
https://docs.tandoor.dev/system/configuration/
### S3 Object Storage
The following table lists the configurable parameters and their default values.
### Global Parameters
| Name | Description | Value |
|------|-------------|-------|
| `nameOverride` | String to partially override the release name | `""` |
| `fullnameOverride` | String to fully override the release name | `""` |
### Image Parameters
| Name | Description | Value |
|------|-------------|-------|
| `image.repository` | Tandoor image repository | `vabene1111/recipes` |
| `image.tag` | Tandoor image tag | `2.3.5` |
| `image.pullPolicy` | Tandoor image pull policy | `IfNotPresent` |
### Deployment Parameters
| Name | Description | Value |
|------|-------------|-------|
| `replicaCount` | Number of Tandoor replicas | `1` |
| `revisionHistoryLimit` | Number of old ReplicaSets to retain | `3` |
### PostgreSQL Parameters
| Name | Description | Value |
|------|-------------|-------|
| `postgresql.host` | PostgreSQL host | `postgresql.default.svc.cluster.local` |
| `postgresql.port` | PostgreSQL port | `5432` |
| `postgresql.database` | PostgreSQL database name | `tandoor` |
| `postgresql.username` | PostgreSQL username | `tandoor` |
| `postgresql.password` | PostgreSQL password (not recommended for production) | `""` |
| `postgresql.existingSecret` | Existing secret with PostgreSQL credentials | `""` |
| `postgresql.passwordKey` | Key in existing secret for PostgreSQL password | `postgresql-password` |
### Security Configuration
| Name | Description | Value |
|------|-------------|-------|
| `config.secretKey.value` | Django secret key (at least 50 characters) | `""` |
| `config.secretKey.existingSecret` | Existing secret for Django secret key | `""` |
| `config.secretKey.secretKey` | Key in existing secret for Django secret key | `secret-key` |
| `config.allowedHosts` | Allowed hosts for HTTP Host Header validation | `*` |
| `config.csrfTrustedOrigins` | CSRF trusted origins | `""` |
| `config.corsAllowOrigins` | Enable CORS allow all origins | `false` |
### Server Configuration
| Name | Description | Value |
|------|-------------|-------|
| `config.tandoorPort` | Port where Tandoor exposes its web server | `8080` |
| `config.gunicornWorkers` | Number of Gunicorn worker processes | `3` |
| `config.gunicornThreads` | Number of Gunicorn threads per worker | `2` |
| `config.gunicornTimeout` | Gunicorn request timeout in seconds | `30` |
| `config.gunicornMedia` | Enable media serving via Gunicorn | `0` |
| `config.timezone` | Application timezone | `UTC` |
| `config.scriptName` | URL path base for subfolder deployments | `""` |
| `config.sessionCookieDomain` | Session cookie domain | `""` |
| `config.sessionCookieName` | Session cookie identifier | `sessionid` |
### Feature Configuration
| Name | Description | Value |
|------|-------------|-------|
| `config.enableSignup` | Allow user registration | `false` |
| `config.enableMetrics` | Enable Prometheus metrics at /metrics | `false` |
| `config.enablePdfExport` | Enable recipe PDF export | `false` |
| `config.sortTreeByName` | Sort keywords/foods alphabetically | `false` |
### Social Authentication
| Name | Description | Value |
|------|-------------|-------|
| `config.socialDefaultAccess` | Space ID for auto-joining new social auth users | `0` |
| `config.socialDefaultGroup` | Default group for new users (guest/user/admin) | `guest` |
| `config.socialProviders` | Comma-separated OAuth provider list | `""` |
| `config.socialAccountProviders` | SOCIALACCOUNT_PROVIDERS JSON (for complex setups) | `""` |
### OpenID Connect (OIDC) Configuration
| Name | Description | Value |
|------|-------------|-------|
| `config.oidc.enabled` | Enable OpenID Connect authentication | `false` |
| `config.oidc.providerId` | Provider ID (e.g., authentik, keycloak) | `authentik` |
| `config.oidc.providerName` | Display name on login page | `Authentik` |
| `config.oidc.clientId` | Client ID from OIDC provider | `""` |
| `config.oidc.clientSecret` | Client Secret from OIDC provider | `""` |
| `config.oidc.serverUrl` | OpenID Connect well-known configuration URL | `""` |
### LDAP Configuration
| Name | Description | Value |
|------|-------------|-------|
| `config.ldap.enabled` | Enable LDAP authentication | `false` |
| `config.ldap.serverUri` | LDAP server URI | `""` |
| `config.ldap.bindDn` | LDAP bind distinguished name | `""` |
| `config.ldap.bindPassword` | LDAP bind password | `""` |
| `config.ldap.userSearchBaseDn` | LDAP user search base | `""` |
| `config.ldap.tlsCacertFile` | LDAP TLS CA certificate file | `""` |
| `config.ldap.startTls` | Enable LDAP StartTLS | `false` |
| `config.ldap.existingSecret` | Existing secret for LDAP credentials | `""` |
| `config.ldap.bindPasswordKey` | Key in existing secret for LDAP password | `ldap-bind-password` |
### Remote User Authentication
| Name | Description | Value |
|------|-------------|-------|
| `config.remoteUserAuth` | Enable REMOTE-USER header authentication | `false` |
```yaml
config:
s3:
enabled: true
bucketName: "tandoor-media"
regionName: "us-east-1"
endpointUrl: "https://minio.example.com"
existingSecret: "tandoor-s3-secret"
```
### Email Configuration
| Name | Description | Value |
|------|-------------|-------|
| `config.email.host` | SMTP server hostname | `""` |
| `config.email.port` | SMTP server port | `25` |
| `config.email.user` | SMTP authentication username | `""` |
| `config.email.password` | SMTP authentication password | `""` |
| `config.email.useTls` | Enable TLS for email | `false` |
| `config.email.useSsl` | Enable SSL for email | `false` |
| `config.email.defaultFrom` | Default from email address | `webmaster@localhost` |
```yaml
config:
email:
host: "smtp.example.com"
port: 587
useTls: true
defaultFrom: "tandoor@example.com"
existingSecret: "tandoor-email-secret"
passwordKey: "email-password"
```
### LDAP Authentication
```yaml
config:
ldap:
enabled: true
serverUri: "ldap://ldap.example.com"
bindDn: "cn=admin,dc=example,dc=com"
bindPassword: "bind-password"
userSearchBaseDn: "ou=users,dc=example,dc=com"
existingSecret: "tandoor-ldap-secret"
bindPasswordKey: "ldap-bind-password"
```
## Parameters
### Global Parameters
| Name | Description | Default |
|------|-------------|---------|
| `nameOverride` | Override the release name | `""` |
| `fullnameOverride` | Fully override the release name | `""` |
### Image Parameters
| Name | Description | Default |
|------|-------------|---------|
| `image.repository` | Tandoor image repository | `vabene1111/recipes` |
| `image.tag` | Image tag | `2.3.5` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
### Deployment Parameters
| Name | Description | Default |
|------|-------------|---------|
| `replicaCount` | Number of replicas | `1` |
| `revisionHistoryLimit` | Revisions to retain | `3` |
| `podSecurityContext.fsGroup` | Filesystem group ID | `0` |
| `containerSecurityContext.runAsUser` | User ID | `0` |
| `containerSecurityContext.runAsGroup` | Group ID | `0` |
| `containerSecurityContext.allowPrivilegeEscalation` | Allow privilege escalation | `false` |
| `nodeSelector` | Node selector | `{}` |
| `tolerations` | Tolerations | `[]` |
| `affinity` | Affinity rules | `{}` |
### Service Parameters
| Name | Description | Default |
|------|-------------|---------|
| `service.type` | Service type | `ClusterIP` |
| `service.port` | Service port | `8080` |
### Ingress Parameters
| Name | Description | Default |
|------|-------------|---------|
| `ingress.enabled` | Enable ingress | `false` |
| `ingress.className` | Ingress class name | `""` |
| `ingress.annotations` | Ingress annotations | See values.yaml |
| `ingress.hosts` | Ingress hosts | See values.yaml |
| `ingress.tls` | TLS configuration | See values.yaml |
### PostgreSQL Configuration (Required)
| Name | Description | Default |
|------|-------------|---------|
| `postgresql.host` | PostgreSQL host | `postgresql.default.svc.cluster.local` |
| `postgresql.port` | PostgreSQL port | `5432` |
| `postgresql.database` | Database name | `tandoor` |
| `postgresql.username` | Username | `tandoor` |
| `postgresql.password` | Password | `""` |
| `postgresql.existingSecret` | Existing secret name | `""` |
| `postgresql.passwordKey` | Key for password in secret | `postgresql-password` |
### Security Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.secretKey.value` | Django secret key (min 50 chars) | `""` |
| `config.secretKey.existingSecret` | Existing secret for secret key | `""` |
| `config.secretKey.secretKey` | Key in secret | `secret-key` |
| `config.allowedHosts` | Allowed HTTP hosts | `*` |
| `config.csrfTrustedOrigins` | CSRF trusted origins | `""` |
| `config.corsAllowOrigins` | Allow all CORS origins | `false` |
### Server Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.tandoorPort` | Web server port | `8080` |
| `config.gunicornWorkers` | Gunicorn workers | `3` |
| `config.gunicornThreads` | Gunicorn threads per worker | `2` |
| `config.gunicornTimeout` | Gunicorn timeout (seconds) | `30` |
| `config.gunicornMedia` | Serve media via Gunicorn | `0` |
| `config.timezone` | Timezone | `UTC` |
| `config.scriptName` | URL path base for subfolder | `""` |
| `config.sessionCookieDomain` | Session cookie domain | `""` |
| `config.sessionCookieName` | Session cookie name | `sessionid` |
### Feature Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.enableSignup` | Allow user registration | `false` |
| `config.enableMetrics` | Enable Prometheus metrics | `false` |
| `config.enablePdfExport` | Enable PDF export | `false` |
| `config.sortTreeByName` | Sort keywords alphabetically | `false` |
### OIDC Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.oidc.enabled` | Enable OIDC | `false` |
| `config.oidc.providerId` | Provider ID | `authentik` |
| `config.oidc.providerName` | Provider display name | `Authentik` |
| `config.oidc.clientId` | Client ID | `""` |
| `config.oidc.clientSecret` | Client secret | `""` |
| `config.oidc.serverUrl` | Well-known configuration URL | `""` |
### LDAP Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.ldap.enabled` | Enable LDAP | `false` |
| `config.ldap.serverUri` | LDAP server URI | `""` |
| `config.ldap.bindDn` | Bind DN | `""` |
| `config.ldap.bindPassword` | Bind password | `""` |
| `config.ldap.userSearchBaseDn` | User search base DN | `""` |
| `config.ldap.tlsCacertFile` | TLS CA cert file | `""` |
| `config.ldap.startTls` | Enable StartTLS | `false` |
| `config.ldap.existingSecret` | Existing secret for LDAP | `""` |
| `config.ldap.bindPasswordKey` | Key for bind password in secret | `ldap-bind-password` |
### Email Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.email.host` | SMTP host | `""` |
| `config.email.port` | SMTP port | `25` |
| `config.email.user` | SMTP username | `""` |
| `config.email.password` | SMTP password | `""` |
| `config.email.useTls` | Enable TLS | `false` |
| `config.email.useSsl` | Enable SSL | `false` |
| `config.email.defaultFrom` | Default from address | `webmaster@localhost` |
| `config.email.accountEmailSubjectPrefix` | Email subject prefix | `[Tandoor Recipes]` |
| `config.email.existingSecret` | Existing secret for email credentials | `""` |
| `config.email.passwordKey` | Key in existing secret for email password | `email-password` |
| `config.email.existingSecret` | Existing secret for email | `""` |
| `config.email.passwordKey` | Key for password in secret | `email-password` |
### S3/Object Storage Configuration
### S3 Storage Configuration
| Name | Description | Value |
|------|-------------|-------|
| `config.s3.enabled` | Enable S3 storage for media files | `false` |
| Name | Description | Default |
|------|-------------|---------|
| `config.s3.enabled` | Enable S3 storage | `false` |
| `config.s3.accessKey` | S3 access key | `""` |
| `config.s3.secretAccessKey` | S3 secret access key | `""` |
| `config.s3.bucketName` | S3 bucket name | `""` |
| `config.s3.regionName` | S3 region name | `""` |
| `config.s3.endpointUrl` | Custom S3 endpoint URL (for MinIO) | `""` |
| `config.s3.customDomain` | CDN/proxy domain for S3 | `""` |
| `config.s3.querystringAuth` | Use signed URLs for S3 objects | `true` |
| `config.s3.regionName` | S3 region | `""` |
| `config.s3.endpointUrl` | Custom S3 endpoint (MinIO) | `""` |
| `config.s3.customDomain` | CDN/proxy domain | `""` |
| `config.s3.querystringAuth` | Use signed URLs | `true` |
| `config.s3.querystringExpire` | Signed URL expiration (seconds) | `3600` |
| `config.s3.existingSecret` | Existing secret for S3 credentials | `""` |
| `config.s3.existingSecret` | Existing secret for S3 | `""` |
### AI Features
### Social Authentication
| Name | Description | Value |
|------|-------------|-------|
| Name | Description | Default |
|------|-------------|---------|
| `config.socialDefaultAccess` | Space ID for auto-join | `0` |
| `config.socialDefaultGroup` | Default group (`guest`/`user`/`admin`) | `guest` |
| `config.socialProviders` | OAuth providers (comma-separated) | `""` |
| `config.remoteUserAuth` | Enable REMOTE-USER header auth | `false` |
### AI Configuration
| Name | Description | Default |
|------|-------------|---------|
| `config.ai.enabled` | Enable AI features | `false` |
| `config.ai.creditsMonthly` | Monthly AI credits per space | `100` |
| `config.ai.rateLimit` | AI API rate limit | `60/hour` |
| `config.ai.creditsMonthly` | Monthly credits per space | `100` |
| `config.ai.rateLimit` | AI rate limit | `60/hour` |
### External Services
| Name | Description | Value |
|------|-------------|-------|
| Name | Description | Default |
|------|-------------|---------|
| `config.fdcApiKey` | Food Data Central API key | `DEMO_KEY` |
| `config.disableExternalConnectors` | Disable all external connectors | `false` |
| `config.disableExternalConnectors` | Disable external connectors | `false` |
| `config.externalConnectorsQueueSize` | External connectors queue size | `100` |
### Rate Limiting
| Name | Description | Value |
|------|-------------|-------|
| Name | Description | Default |
|------|-------------|---------|
| `config.ratelimitUrlImportRequests` | Rate limit for URL imports | `""` |
| `config.drfThrottleRecipeUrlImport` | DRF throttle for recipe URL import | `60/hour` |
### Space Defaults
### Space & User Defaults
| Name | Description | Value |
|------|-------------|-------|
| Name | Description | Default |
|------|-------------|---------|
| `config.spaceDefaultMaxRecipes` | Max recipes per space (0=unlimited) | `0` |
| `config.spaceDefaultMaxUsers` | Max users per space (0=unlimited) | `0` |
| `config.spaceDefaultMaxFiles` | Max file storage in MB (0=unlimited) | `0` |
| `config.spaceDefaultAllowSharing` | Allow public recipe sharing | `true` |
### User Preference Defaults
| Name | Description | Value |
|------|-------------|-------|
| `config.spaceDefaultAllowSharing` | Allow public sharing | `true` |
| `config.fractionPrefDefault` | Default fraction display | `false` |
| `config.commentPrefDefault` | Enable comments by default | `true` |
| `config.commentPrefDefault` | Comments enabled by default | `true` |
| `config.stickyNavPrefDefault` | Sticky navbar by default | `true` |
| `config.maxOwnedSpacesPrefDefault` | Max spaces per user | `100` |
### Cosmetic Configuration
### Performance & Cosmetic
| Name | Description | Value |
|------|-------------|-------|
| Name | Description | Default |
|------|-------------|---------|
| `config.shoppingMinAutosyncInterval` | Min auto-sync interval (minutes) | `5` |
| `config.exportFileCacheDuration` | Export cache duration (seconds) | `600` |
| `config.unauthenticatedThemeFromSpace` | Space ID for unauthenticated theme | `0` |
| `config.forceThemeFromSpace` | Space ID to enforce theme globally | `0` |
### Performance Configuration
| Name | Description | Value |
|------|-------------|-------|
| `config.shoppingMinAutosyncInterval` | Min auto-sync interval (minutes) | `5` |
| `config.exportFileCacheDuration` | Export cache duration (seconds) | `600` |
### Legal URLs
| Name | Description | Value |
|------|-------------|-------|
| Name | Description | Default |
|------|-------------|---------|
| `config.termsUrl` | Terms of service URL | `""` |
| `config.privacyUrl` | Privacy policy URL | `""` |
| `config.imprintUrl` | Legal imprint URL | `""` |
### hCaptcha Configuration
| Name | Description | Value |
|------|-------------|-------|
| Name | Description | Default |
|------|-------------|---------|
| `config.hcaptcha.siteKey` | hCaptcha site key | `""` |
| `config.hcaptcha.secret` | hCaptcha secret key | `""` |
| `config.hcaptcha.secret` | hCaptcha secret | `""` |
| `config.hcaptcha.existingSecret` | Existing secret for hCaptcha | `""` |
### Debugging
| Name | Description | Value |
|------|-------------|-------|
| `config.debug` | Enable Django debug mode | `false` |
| `config.debugToolbar` | Enable Django Debug Toolbar | `false` |
| `config.sqlDebug` | Enable SQL debug output | `false` |
| `config.logLevel` | Application log level | `WARNING` |
| `config.gunicornLogLevel` | Gunicorn log level | `info` |
### Service Parameters
| Name | Description | Value |
|------|-------------|-------|
| `service.type` | Kubernetes Service type | `ClusterIP` |
| `service.port` | Service HTTP port | `8080` |
### Ingress Parameters
| Name | Description | Value |
|------|-------------|-------|
| `ingress.enabled` | Enable ingress | `false` |
| `ingress.className` | Ingress class name | `""` |
| `ingress.annotations` | Ingress annotations | See values.yaml |
| `ingress.hosts` | Ingress hosts configuration | See values.yaml |
| `ingress.tls` | Ingress TLS configuration | See values.yaml |
### Persistence Parameters
| Name | Description | Value |
|------|-------------|-------|
| `persistence.staticfiles.enabled` | Enable static files persistence | `true` |
| `persistence.staticfiles.existingClaim` | Use existing PVC for static files | `""` |
| `persistence.staticfiles.storageClass` | Storage class for static files | `""` |
| `persistence.staticfiles.accessMode` | Access mode for static files PVC | `ReadWriteOnce` |
| `persistence.staticfiles.size` | Size of static files PVC | `1Gi` |
| `persistence.mediafiles.enabled` | Enable media files persistence | `true` |
| `persistence.mediafiles.existingClaim` | Use existing PVC for media files | `""` |
| `persistence.mediafiles.storageClass` | Storage class for media files | `""` |
| `persistence.mediafiles.accessMode` | Access mode for media files PVC | `ReadWriteOnce` |
| `persistence.mediafiles.size` | Size of media files PVC | `5Gi` |
| Name | Description | Default |
|------|-------------|---------|
| `persistence.staticfiles.enabled` | Enable static files PVC | `true` |
| `persistence.staticfiles.existingClaim` | Existing PVC for static files | `""` |
| `persistence.staticfiles.storageClass` | Storage class | `""` |
| `persistence.staticfiles.accessMode` | Access mode | `ReadWriteOnce` |
| `persistence.staticfiles.size` | PVC size | `1Gi` |
| `persistence.mediafiles.enabled` | Enable media files PVC | `true` |
| `persistence.mediafiles.existingClaim` | Existing PVC for media files | `""` |
| `persistence.mediafiles.storageClass` | Storage class | `""` |
| `persistence.mediafiles.accessMode` | Access mode | `ReadWriteOnce` |
| `persistence.mediafiles.size` | PVC size | `5Gi` |
### Pod Security Context
### Resource Parameters
| Name | Description | Value |
|------|-------------|-------|
| `podSecurityContext.runAsNonRoot` | Run as non-root user | `true` |
| `podSecurityContext.runAsUser` | User ID to run as | `1000` |
| `podSecurityContext.fsGroup` | Group ID for filesystem | `1000` |
| Name | Description | Default |
|------|-------------|---------|
| `resources` | Resource limits and requests | `{}` |
### Container Security Context
### Health Check Parameters
| Name | Description | Value |
|------|-------------|-------|
| `containerSecurityContext.allowPrivilegeEscalation` | Allow privilege escalation | `false` |
| `containerSecurityContext.readOnlyRootFilesystem` | Read-only root filesystem | `false` |
| Name | Description | Default |
|------|-------------|---------|
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.path` | Liveness probe path | `/` |
| `probes.liveness.initialDelaySeconds` | Liveness initial delay | `30` |
| `probes.liveness.periodSeconds` | Liveness period | `10` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.path` | Readiness probe path | `/` |
| `probes.readiness.initialDelaySeconds` | Readiness initial delay | `15` |
| `probes.readiness.periodSeconds` | Readiness period | `5` |
### Autoscaling Parameters
| Name | Description | Value |
|------|-------------|-------|
| `autoscaling.enabled` | Enable autoscaling | `false` |
| `autoscaling.minReplicas` | Minimum replicas | `1` |
| `autoscaling.maxReplicas` | Maximum replicas | `3` |
| `autoscaling.targetCPUUtilizationPercentage` | Target CPU utilization | `80` |
| `autoscaling.targetMemoryUtilizationPercentage` | Target memory utilization | `80` |
| Name | Description | Default |
|------|-------------|---------|
| `autoscaling.enabled` | Enable HPA | `false` |
| `autoscaling.minReplicas` | Min replicas | `1` |
| `autoscaling.maxReplicas` | Max replicas | `3` |
| `autoscaling.targetCPUUtilizationPercentage` | Target CPU | `80` |
| `autoscaling.targetMemoryUtilizationPercentage` | Target memory | `80` |
### Probes Configuration
### Debugging
| Name | Description | Value |
|------|-------------|-------|
| `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.initialDelaySeconds` | Initial delay for liveness probe | `30` |
| `probes.liveness.periodSeconds` | Period for liveness probe | `10` |
| `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.initialDelaySeconds` | Initial delay for readiness probe | `15` |
| `probes.readiness.periodSeconds` | Period for readiness probe | `5` |
| Name | Description | Default |
|------|-------------|---------|
| `config.debug` | Enable Django debug mode | `false` |
| `config.debugToolbar` | Enable Debug Toolbar | `false` |
| `config.sqlDebug` | Enable SQL debug | `false` |
| `config.logLevel` | Application log level | `WARNING` |
| `config.gunicornLogLevel` | Gunicorn log level | `info` |
### Additional Configuration
| Name | Description | Value |
|------|-------------|-------|
| `env` | Additional environment variables | `[]` |
| `extraEnvFrom` | Additional environment variables from secrets | `[]` |
| `extraVolumes` | Additional volumes | `[]` |
| `extraVolumeMounts` | Additional volume mounts | `[]` |
| `nodeSelector` | Node selector | `{}` |
| `tolerations` | Tolerations | `[]` |
| `affinity` | Affinity rules | `{}` |
| Name | Description | Default |
|------|-------------|---------|
| `env` | Extra environment variables | `[]` |
| `extraEnvFrom` | Extra env from secrets/configmaps | `[]` |
| `extraVolumes` | Extra volumes | `[]` |
| `extraVolumeMounts` | Extra volume mounts | `[]` |
## Uninstalling the Chart
## Troubleshooting
- **CSRF Errors**: Set `config.csrfTrustedOrigins` to your domain URL including the scheme
- **Login Issues**: Verify OIDC/LDAP configuration and callback URLs
- **Missing Media**: Check persistence configuration and S3 connectivity if enabled
```bash
helm uninstall tandoor
```
**Note:** PVCs are not automatically deleted. To remove them:
```bash
kubectl delete pvc -l app.kubernetes.io/name=tandoor
kubectl logs -f deployment/tandoor
kubectl describe pod -l app.kubernetes.io/name=tandoor
```
## Links
@ -425,3 +452,4 @@ kubectl delete pvc -l app.kubernetes.io/name=tandoor
- [Tandoor Recipes GitHub](https://github.com/TandoorRecipes/recipes)
- [Tandoor Documentation](https://docs.tandoor.dev/)
- [Configuration Reference](https://docs.tandoor.dev/system/configuration/)
- [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/tandoor)