Compare commits

...

17 Commits

Author SHA1 Message Date
d3cdd77cc6 added support for using existing pvc 2025-12-17 16:14:10 +01:00
4c8179f9cc fixed issue #2 2025-12-17 15:59:50 +01:00
7be50d4890 added option to add extra env variables 2025-12-11 13:05:15 +01:00
3a61591220 fixed bug with norish 2025-12-11 12:40:17 +01:00
25265eb94f deleted values file 2025-12-11 10:12:35 +01:00
8e34bd33dd release of norish 2025-12-08 17:02:52 +01:00
4cb45e3013 release paperless ngx 0.0.2 fixed bug with redis configuration 2025-10-26 18:28:25 +01:00
e65df72663 Merge branch 'main' of https://github.com/rtomik/helm-charts 2025-10-13 09:27:00 +02:00
33f865a892 edited example 2025-10-13 09:26:54 +02:00
2ecf4aeec0 Merge pull request #1 from piontec/add-pvc-selector
chg: add support for PVC selector
2025-10-11 14:40:57 +02:00
720a81d343 fixed bug with external postgresql v 1.0.4 2025-10-11 14:29:26 +02:00
c9b25918d5 chg: add support for PVC selector 2025-10-01 15:03:41 +02:00
c81bb1bbd1 fixed bug with db configuration donetick helm chart 2025-09-22 09:19:55 +02:00
741401a79d enhance donetick helm chart with missing features and repository standards… 2025-09-21 18:31:04 +02:00
509492560e mealie helm chart release with upgraded version 2025-09-20 19:31:52 +02:00
af1ecd86cb Release paperlessngx helmchart v0.0.1 2025-09-14 17:37:28 +02:00
85b6787314 Fixed bug with joplin server helm chart with secret values 2025-08-25 09:08:40 +02:00
49 changed files with 4790 additions and 91 deletions

View File

@ -2,8 +2,8 @@ apiVersion: v2
name: donetick
description: Donetick helm chart for Kubernetes
type: application
version: 1.0.1
appVersion: "v0.1.38"
version: 1.0.4
appVersion: "v0.1.60"
maintainers:
- name: Richard Tomik
email: no@m.com

View File

@ -27,12 +27,192 @@ $ helm install donetick donetick-chart/donetick
> **Tip**: List all releases using `helm list`
## Configuration Examples
### Basic Installation with SQLite (Default)
```bash
helm install donetick donetick-chart/donetick
```
### Installation with External PostgreSQL
Create a values file for PostgreSQL configuration:
```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"
port: 5432
name: "donetick"
secrets:
existingSecret: "donetick-postgres-secret"
userKey: "username"
passwordKey: "password"
# Use existing secret for JWT
jwt:
existingSecret: "donetick-jwt-secret"
secretKey: "jwt-secret"
session_time: "168h"
max_refresh: "168h"
# OAuth2 configuration with secrets
oauth2:
existingSecret: "donetick-oauth-secret"
clientIdKey: "client-id"
clientSecretKey: "client-secret"
auth_url: "https://your-oauth-provider.com/auth"
token_url: "https://your-oauth-provider.com/token"
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
memory: 512Mi
requests:
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:
- 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'
# 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
helm uninstall donetick
```
## Parameters
@ -49,7 +229,7 @@ $ helm uninstall donetick
| Name | Description | Value |
|-------------------------|--------------------------------------------------------------------------------------|--------------------|
| `image.repository` | Donetick image repository | `donetick/donetick` |
| `image.tag` | Donetick image tag | `latest` |
| `image.tag` | Donetick image tag | `v0.1.60` |
| `image.pullPolicy` | Donetick image pull policy | `IfNotPresent` |
| `imagePullSecrets` | Global Docker registry secret names as an array | `[]` |
@ -62,12 +242,9 @@ $ helm uninstall donetick
| `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.existingSecret` | Name of existing secret for database credentials | `""` |
| `config.database.hostKey` | Key in the existing secret for database host | `"db-host"` |
| `config.database.portKey` | Key in the existing secret for database port | `"db-port"` |
| `config.database.userKey` | Key in the existing secret for database user | `"db-user"` |
| `config.database.passwordKey` | Key in the existing secret for database password | `"db-password"` |
| `config.database.nameKey` | Key in the existing secret for database name | `"db-name"` |
| `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"` |
### Deployment parameters
@ -90,7 +267,12 @@ $ helm uninstall donetick
| `service.type` | Kubernetes Service type | `ClusterIP` |
| `service.port` | Service HTTP port | `2021` |
| `service.annotations` | Additional annotations for Service | `{}` |
| `service.nodePort` | Service HTTP node port (when applicable) | `""` |
### Pod Configuration
| Name | Description | Value |
|----------------------------|------------------------------------------------------|-------------|
| `podAnnotations` | Additional annotations for pods | `{}` |
### Ingress parameters
@ -108,7 +290,174 @@ $ helm uninstall donetick
| Name | Description | Value |
|-------------------------------|------------------------------------------------------|---------------|
| `persistence.enabled` | Enable persistence using PVC | `true` |
| `persistence.storageClass` | PVC Storage Class | `"longhorn"` |
| `persistence.enabled` | Enable persistence using PVC | `false` |
| `persistence.storageClass` | PVC Storage Class | `""` |
| `persistence.accessMode` | PVC Access Mode | `ReadWriteOnce` |
| `persistence.size` |
| `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` |
### 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) | `""` |
### 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` |
### Server Configuration
| Name | Description | Value |
|----------------------------------------|------------------------------------------------------|---------------|
| `config.server.port` | Server port | `2021` |
| `config.server.read_timeout` | Server read timeout | `10s` |
| `config.server.write_timeout` | Server write timeout | `10s` |
| `config.server.rate_period` | Rate limiting period | `60s` |
| `config.server.rate_limit` | Rate limiting requests per period | `300` |
| `config.server.serve_frontend` | Serve frontend files | `true` |
### Feature Flags
| Name | Description | Value |
|----------------------------------------|------------------------------------------------------|---------------|
| `config.features.notifications` | Enable notifications | `true` |
| `config.features.realtime` | Enable real-time features | `true` |
| `config.features.oauth` | Enable OAuth authentication | `false` |
## Database Setup
### PostgreSQL Requirements
When using PostgreSQL, ensure you have:
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;
```
2. **Network Access**: Ensure Donetick can reach your PostgreSQL instance
3. **Proper Credentials**: Configure database credentials in values or secrets
### 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
## Troubleshooting
### Common Issues
#### 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
```
#### 2. Database Connection Issues
**Error**: Database connection failures
**Solutions**:
- Verify PostgreSQL is running and accessible
- Check database credentials in secrets
- Ensure database name exists
- Verify network policies allow connection
#### 3. JWT Secret Issues
**Error**: 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"
```
#### 4. CORS Issues
**Error**: Cross-origin request blocked
**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
```
## 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.

View File

@ -22,15 +22,18 @@ data:
{{- if .Values.config.database.migration_retry }}
migration_retry: {{ .Values.config.database.migration_retry }}
{{- end }}
migration_timeout: {{ .Values.config.database.migration_timeout | default "300s" | quote }}
{{- if eq .Values.config.database.type "postgres" }}
{{- if not .Values.config.database.existingSecret }}
host: {{ .Values.config.database.host | quote }}
port: {{ .Values.config.database.port }}
name: {{ .Values.config.database.name | quote }}
{{- if not .Values.config.database.secrets.existingSecret }}
user: {{ .Values.config.database.user | quote }}
password: {{ .Values.config.database.password | quote }}
name: {{ .Values.config.database.name | quote }}
{{- else }}
# Database credentials will be injected via environment variables from Secret
# Reference environment variables for database credentials
user: "$DT_DATABASE_USER"
password: "$DT_DATABASE_PASSWORD"
{{- end }}
{{- end }}
jwt:
@ -74,4 +77,20 @@ data:
token_url: {{ .Values.config.oauth2.token_url | default "" | quote }}
user_info_url: {{ .Values.config.oauth2.user_info_url | default "" | quote }}
redirect_url: {{ .Values.config.oauth2.redirect_url | default "" | quote }}
name: {{ .Values.config.oauth2.name | default "" | quote }}
name: {{ .Values.config.oauth2.name | default "" | quote }}
realtime:
max_connections: {{ .Values.config.realtime.max_connections }}
ping_interval: {{ .Values.config.realtime.ping_interval | quote }}
pong_wait: {{ .Values.config.realtime.pong_wait | quote }}
write_wait: {{ .Values.config.realtime.write_wait | quote }}
max_message_size: {{ .Values.config.realtime.max_message_size }}
logging:
level: {{ .Values.config.logging.level | quote }}
format: {{ .Values.config.logging.format | quote }}
storage:
type: {{ .Values.config.storage.type | quote }}
path: {{ .Values.config.storage.path | quote }}
features:
notifications: {{ .Values.config.features.notifications }}
realtime: {{ .Values.config.features.realtime }}
oauth: {{ .Values.config.features.oauth }}

View File

@ -50,6 +50,17 @@ spec:
- name: http
containerPort: {{ .Values.config.server.port }}
protocol: TCP
{{- if .Values.probes.startup.enabled }}
startupProbe:
httpGet:
path: {{ .Values.probes.startup.path }}
port: http
initialDelaySeconds: {{ .Values.probes.startup.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.startup.periodSeconds }}
timeoutSeconds: {{ .Values.probes.startup.timeoutSeconds }}
failureThreshold: {{ .Values.probes.startup.failureThreshold }}
successThreshold: {{ .Values.probes.startup.successThreshold }}
{{- end }}
{{- if .Values.probes.liveness.enabled }}
livenessProbe:
httpGet:
@ -77,15 +88,44 @@ spec:
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- if or .Values.config.jwt.existingSecret .Values.config.oauth2.existingSecret .Values.config.database.existingSecret }}
# Secret-based environment variables
# Database configuration environment variables
{{- if eq .Values.config.database.type "postgres" }}
- name: DT_DATABASE_TYPE
value: "postgres"
- name: DT_DATABASE_HOST
value: {{ .Values.config.database.host | quote }}
- name: DT_DATABASE_PORT
value: {{ .Values.config.database.port | quote }}
- name: DT_DATABASE_NAME
value: {{ .Values.config.database.name | quote }}
{{- if .Values.config.database.secrets.existingSecret }}
- name: DT_DATABASE_USER
valueFrom:
secretKeyRef:
name: {{ .Values.config.database.secrets.existingSecret }}
key: {{ .Values.config.database.secrets.userKey }}
- name: DT_DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.config.database.secrets.existingSecret }}
key: {{ .Values.config.database.secrets.passwordKey }}
{{- end }}
{{- else }}
- name: DT_DATABASE_TYPE
value: {{ .Values.config.database.type | quote }}
{{- end }}
# JWT configuration
{{- if .Values.config.jwt.existingSecret }}
- name: DT_JWT_SECRET
valueFrom:
secretKeyRef:
name: {{ .Values.config.jwt.existingSecret }}
key: {{ .Values.config.jwt.secretKey }}
{{- else }}
- name: DT_JWT_SECRET
value: {{ .Values.config.jwt.secret | quote }}
{{- end }}
# OAuth2 configuration
{{- if .Values.config.oauth2.existingSecret }}
- name: DT_OAUTH2_CLIENT_ID
valueFrom:
@ -98,34 +138,6 @@ spec:
name: {{ .Values.config.oauth2.existingSecret }}
key: {{ .Values.config.oauth2.clientSecretKey }}
{{- end }}
{{- if and .Values.config.database.existingSecret (eq .Values.config.database.type "postgres") }}
- name: DT_DB_HOST
valueFrom:
secretKeyRef:
name: {{ .Values.config.database.existingSecret }}
key: {{ .Values.config.database.hostKey }}
- name: DT_DB_PORT
valueFrom:
secretKeyRef:
name: {{ .Values.config.database.existingSecret }}
key: {{ .Values.config.database.portKey }}
- name: DT_DB_USER
valueFrom:
secretKeyRef:
name: {{ .Values.config.database.existingSecret }}
key: {{ .Values.config.database.userKey }}
- name: DT_DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.config.database.existingSecret }}
key: {{ .Values.config.database.passwordKey }}
- name: DT_DB_NAME
valueFrom:
secretKeyRef:
name: {{ .Values.config.database.existingSecret }}
key: {{ .Values.config.database.nameKey }}
{{- end }}
{{- end }}
{{- with .Values.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
@ -148,9 +160,14 @@ spec:
- name: config
configMap:
name: {{ include "donetick.fullname" . }}-configmap
{{- if .Values.persistence.enabled }}
- name: data
persistentVolumeClaim:
claimName: {{ include "donetick.fullname" . }}-data
{{- else }}
- name: data
emptyDir: {}
{{- end }}
{{- if not .Values.containerSecurityContext.readOnlyRootFilesystem }}
- name: tmp
emptyDir: {}

View File

@ -1,4 +1,4 @@
{{- if or (not .Values.config.jwt.existingSecret) (and (not .Values.config.oauth2.existingSecret) (or .Values.config.oauth2.client_id .Values.config.oauth2.client_secret)) (and (eq .Values.config.database.type "postgres") (not .Values.config.database.existingSecret)) }}
{{- if or (not .Values.config.jwt.existingSecret) (and (not .Values.config.oauth2.existingSecret) (or .Values.config.oauth2.client_id .Values.config.oauth2.client_secret)) (and (eq .Values.config.database.type "postgres") (not .Values.config.database.secrets.existingSecret)) }}
apiVersion: v1
kind: Secret
metadata:
@ -10,8 +10,8 @@ data:
{{- if not .Values.config.jwt.existingSecret }}
{{ .Values.config.jwt.secretKey }}: {{ .Values.config.jwt.secret | b64enc }}
{{- end }}
{{- if and (eq .Values.config.database.type "postgres") (not .Values.config.database.existingSecret) }}
{{ .Values.config.database.passwordKey }}: {{ .Values.config.database.password | b64enc }}
{{- if and (eq .Values.config.database.type "postgres") (not .Values.config.database.secrets.existingSecret) }}
{{ .Values.config.database.secrets.passwordKey }}: {{ .Values.config.database.password | b64enc }}
{{- end }}
{{- if and (not .Values.config.oauth2.existingSecret) .Values.config.oauth2.client_id }}
{{ .Values.config.oauth2.clientIdKey }}: {{ .Values.config.oauth2.client_id | b64enc }}

View File

@ -4,6 +4,10 @@ metadata:
name: {{ include "donetick.fullname" . }}
labels:
{{- include "donetick.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:

View File

@ -5,8 +5,10 @@ fullnameOverride: ""
## Image settings
image:
repository: donetick/donetick
tag: "v0.1.38"
tag: "v0.1.60"
pullPolicy: IfNotPresent
imagePullSecrets: []
## Deployment settings
replicaCount: 1
@ -34,10 +36,14 @@ nodeSelector: {}
tolerations: []
affinity: {}
## Pod annotations
podAnnotations: {}
## Service settings
service:
type: ClusterIP
port: 2021
annotations: {}
## Ingress settings
ingress:
@ -85,16 +91,28 @@ extraVolumeMounts: []
extraVolumes: []
## Resource limits and requests
# resources:
# limits:
# cpu: 500m
# memory: 512Mi
# requests:
# cpu: 100m
# memory: 128Mi
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 500m
# memory: 512Mi
# requests:
# cpu: 100m
# memory: 128Mi
## Application health checks
probes:
startup:
enabled: true
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 15
failureThreshold: 80
successThreshold: 1
path: /health
liveness:
enabled: true
initialDelaySeconds: 30
@ -139,21 +157,18 @@ config:
# Migration options
migration_skip: false # Set to true to skip database migrations
migration_retry: 3 # Number of retries for failed migrations
# These are only required for postgres - direct configuration
migration_timeout: "600s" # Timeout for database migrations (default: 10 minutes)
# These are only required for postgres
host: ""
port: 5432
user: ""
password: ""
name: ""
# Secret configuration for database credentials
existingSecret: "" # Name of existing Kubernetes secret
hostKey: "db-host" # Key in the secret for database host
portKey: "db-port" # Key in the secret for database port
userKey: "db-user" # Key in the secret for database user
passwordKey: "db-password" # Key in the secret for database password
nameKey: "db-name" # Key in the secret for database name
# Secret configuration for postgres credentials
secrets:
existingSecret: "" # Name of existing Kubernetes secret containing postgres credentials
userKey: "username" # Key in the secret for database username
passwordKey: "password" # Key in the secret for database password
# Security settings
# For production, use a generated secret and store in a Kubernetes Secret
@ -207,4 +222,28 @@ config:
token_url: ""
user_info_url: ""
redirect_url: ""
name: ""
name: ""
# Real-time configuration
realtime:
max_connections: 100
ping_interval: "30s"
pong_wait: "60s"
write_wait: "10s"
max_message_size: 512
# Logging configuration
logging:
level: "info"
format: "json"
# Storage configuration
storage:
type: "local"
path: "/donetick-data/uploads"
# Feature flags
features:
notifications: true
realtime: true
oauth: false

View File

@ -2,7 +2,7 @@ apiVersion: v2
name: joplin-server
description: Joplin Server helm chart for Kubernetes - Note-taking and synchronization server
type: application
version: 0.0.1
version: 0.0.2
appVersion: "3.4.2"
maintainers:
- name: Richard Tomik

View File

@ -104,9 +104,9 @@ $ helm uninstall joplin-server
| `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 | `host` |
| `postgresql.external.portKey` | Key in the secret for port | `port` |
| `postgresql.external.databaseKey` | Key in the secret for database name | `database` |
| `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
@ -308,6 +308,7 @@ joplin:
### Using Kubernetes Secrets
#### Full Secret Configuration
```yaml
postgresql:
external:
@ -326,6 +327,20 @@ joplin:
passwordKey: "password"
```
#### Mixed Configuration (Host in values, credentials in secret)
```yaml
postgresql:
external:
enabled: true
host: "postgres-cluster-pooler.dbs.svc.cluster.local"
port: 5432
database: "joplin-server"
existingSecret: "joplin-db-credentials"
userKey: "username"
passwordKey: "password"
# hostKey, portKey, databaseKey left empty - using values above
```
### S3 Storage Configuration
```yaml

View File

@ -77,7 +77,7 @@ spec:
{{- end }}
{{- if .Values.postgresql.external.enabled }}
- name: POSTGRES_HOST
{{- if .Values.postgresql.external.existingSecret }}
{{- if and .Values.postgresql.external.existingSecret .Values.postgresql.external.hostKey }}
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.external.existingSecret }}
@ -86,7 +86,7 @@ spec:
value: {{ .Values.postgresql.external.host | quote }}
{{- end }}
- name: POSTGRES_PORT
{{- if .Values.postgresql.external.existingSecret }}
{{- if and .Values.postgresql.external.existingSecret .Values.postgresql.external.portKey }}
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.external.existingSecret }}
@ -95,7 +95,7 @@ spec:
value: {{ .Values.postgresql.external.port | quote }}
{{- end }}
- name: POSTGRES_DATABASE
{{- if .Values.postgresql.external.existingSecret }}
{{- if and .Values.postgresql.external.existingSecret .Values.postgresql.external.databaseKey }}
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.external.existingSecret }}
@ -104,7 +104,7 @@ spec:
value: {{ .Values.postgresql.external.database | quote }}
{{- end }}
- name: POSTGRES_USER
{{- if .Values.postgresql.external.existingSecret }}
{{- if and .Values.postgresql.external.existingSecret .Values.postgresql.external.userKey }}
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.external.existingSecret }}
@ -113,7 +113,7 @@ spec:
value: {{ .Values.postgresql.external.user | quote }}
{{- end }}
- name: POSTGRES_PASSWORD
{{- if .Values.postgresql.external.existingSecret }}
{{- if and .Values.postgresql.external.existingSecret .Values.postgresql.external.passwordKey }}
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.external.existingSecret }}

View File

@ -128,9 +128,10 @@ postgresql:
existingSecret: ""
userKey: "username"
passwordKey: "password"
hostKey: "host"
portKey: "port"
databaseKey: "database"
# Optional: only set if host/port/database are also in the secret
hostKey: ""
portKey: ""
databaseKey: ""
## Joplin Server Configuration
joplin:

View File

@ -2,8 +2,8 @@ apiVersion: v2
name: mealie
description: Mealie helm chart for Kubernetes - Recipe management and meal planning
type: application
version: 0.0.1
appVersion: "v3.1.1"
version: 0.0.2
appVersion: "v3.2.1"
maintainers:
- name: Richard Tomik
email: no@m.com

View File

@ -18,4 +18,10 @@ spec:
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- end }}
{{- if .Values.persistence.selector }}
{{- with .Values.persistence.selector }}
selector:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -5,7 +5,7 @@ fullnameOverride: ""
## Image settings
image:
repository: ghcr.io/mealie-recipes/mealie
tag: "v3.1.1"
tag: "v3.2.1"
pullPolicy: IfNotPresent
## Deployment settings

17
charts/norish/Chart.yaml Normal file
View File

@ -0,0 +1,17 @@
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"
maintainers:
- name: Richard Tomik
email: no@m.com
keywords:
- recipe
- meal-planning
- food
- norish
home: https://github.com/rtomik/helm-charts
sources:
- https://github.com/norishapp/norish

544
charts/norish/readme.md Normal file
View File

@ -0,0 +1,544 @@
cl# 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.
**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).
## Prerequisites
- Kubernetes 1.19+
- Helm 3.0+
- **PostgreSQL database server** (required)
- PV provisioner support in the underlying infrastructure (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
```
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
### 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:
```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>"
# passwordAuthEnabled defaults to true when no OAuth/OIDC is configured
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: Installation with OIDC
For enterprise deployments with an external identity provider:
```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"
config:
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:
```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-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
```yaml
config:
auth:
oidc:
enabled: true
name: "Authentik" # Display name
issuer: "https://auth.example.com/application/o/norish/"
clientId: "<your-client-id>"
clientSecret: "<your-client-secret>"
```
### GitHub OAuth
1. Create a GitHub OAuth App at https://github.com/settings/developers
2. Set Authorization callback URL to: `https://norish.example.com/api/auth/callback/github`
```yaml
config:
auth:
github:
enabled: true
clientId: "<your-github-client-id>"
clientSecret: "<your-github-client-secret>"
```
### Google OAuth
1. Create OAuth credentials at https://console.cloud.google.com/apis/credentials
2. Set Authorized redirect URI to: `https://norish.example.com/api/auth/callback/google`
```yaml
config:
auth:
google:
enabled: true
clientId: "<your-google-client-id>"
clientSecret: "<your-google-client-secret>"
```
## Troubleshooting
### Check Pod Status
```bash
kubectl get pods -l app.kubernetes.io/name=norish
kubectl logs -l app.kubernetes.io/name=norish
```
### Check Database Connection
```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.

View File

@ -0,0 +1,74 @@
Thank you for installing {{ .Chart.Name }}!
Your release is named {{ .Release.Name }}.
To learn more about the release, try:
$ helm status {{ .Release.Name }} -n {{ .Release.Namespace }}
$ helm get all {{ .Release.Name }} -n {{ .Release.Namespace }}
{{- if .Values.ingress.enabled }}
Application URL:
{{- range .Values.ingress.hosts }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}{{ range .paths }}{{ .path }}{{ end }}
{{- end }}
{{- else }}
Get the application URL by running these commands:
{{- if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "norish.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "norish.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "norish.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "norish.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}
{{- end }}
IMPORTANT CONFIGURATION NOTES:
1. Database Configuration:
{{- if .Values.database.host }}
Using external PostgreSQL at: {{ .Values.database.host }}:{{ .Values.database.port }}
{{- else }}
⚠️ WARNING: Database host is not configured!
Configure database.host to point to your PostgreSQL server.
{{- end }}
2. Master Key:
{{- if .Values.config.masterKey.existingSecret }}
Using existing secret: {{ .Values.config.masterKey.existingSecret }}
{{- else }}
{{- if not .Values.config.masterKey.value }}
⚠️ WARNING: Master key is not set! Generate one with: openssl rand -base64 32
{{- else }}
Master key configured from values.yaml
{{- end }}
{{- end }}
3. Authentication:
{{- if or .Values.config.auth.oidc.enabled .Values.config.auth.github.enabled .Values.config.auth.google.enabled }}
{{- if .Values.config.auth.oidc.enabled }}
- OIDC provider: {{ .Values.config.auth.oidc.name }}
{{- end }}
{{- if .Values.config.auth.github.enabled }}
- GitHub OAuth enabled
{{- end }}
{{- if .Values.config.auth.google.enabled }}
- Google OAuth enabled
{{- end }}
After first login, configure additional providers in Settings → Admin
{{- else }}
⚠️ WARNING: No authentication provider configured!
Configure ONE provider (OIDC, GitHub, or Google) to create your admin account.
{{- end }}
For more information, visit: https://github.com/norishapp/norish

View File

@ -0,0 +1,57 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "norish.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "norish.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s" $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "norish.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "norish.labels" -}}
helm.sh/chart: {{ include "norish.chart" . }}
{{ include "norish.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "norish.selectorLabels" -}}
app.kubernetes.io/name: {{ include "norish.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Database connection URL
*/}}
{{- define "norish.databaseUrl" -}}
{{- $username := .Values.database.username }}
{{- $password := .Values.database.password }}
{{- $host := .Values.database.host }}
{{- $port := .Values.database.port }}
{{- $database := .Values.database.name }}
{{- printf "postgres://%s:%s@%s:%d/%s" $username $password $host (int $port) $database }}
{{- end }}

View File

@ -0,0 +1,282 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "norish.fullname" . }}
labels:
{{- include "norish.labels" . | nindent 4 }}
app.kubernetes.io/component: app
annotations:
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
spec:
replicas: {{ .Values.replicaCount }}
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
selector:
matchLabels:
{{- include "norish.selectorLabels" . | nindent 6 }}
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
{{- include "norish.selectorLabels" . | nindent 8 }}
annotations:
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
{{- if .Values.probes.startup.enabled }}
startupProbe:
httpGet:
path: {{ .Values.probes.startup.path }}
port: http
initialDelaySeconds: {{ .Values.probes.startup.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.startup.periodSeconds }}
timeoutSeconds: {{ .Values.probes.startup.timeoutSeconds }}
failureThreshold: {{ .Values.probes.startup.failureThreshold }}
successThreshold: {{ .Values.probes.startup.successThreshold }}
{{- end }}
{{- if .Values.probes.liveness.enabled }}
livenessProbe:
httpGet:
path: {{ .Values.probes.liveness.path }}
port: http
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
successThreshold: {{ .Values.probes.liveness.successThreshold }}
{{- end }}
{{- if .Values.probes.readiness.enabled }}
readinessProbe:
httpGet:
path: {{ .Values.probes.readiness.path }}
port: http
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
successThreshold: {{ .Values.probes.readiness.successThreshold }}
{{- end }}
env:
- name: AUTH_URL
value: {{ .Values.config.authUrl | quote }}
{{- if .Values.chrome.enabled }}
- name: CHROME_WS_ENDPOINT
value: "ws://localhost:{{ .Values.chrome.port }}"
{{- end }}
{{- if .Values.config.logLevel }}
- name: NEXT_PUBLIC_LOG_LEVEL
value: {{ .Values.config.logLevel | quote }}
{{- end }}
{{- if .Values.config.trustedOrigins }}
- name: TRUSTED_ORIGINS
value: {{ .Values.config.trustedOrigins | quote }}
{{- end }}
{{- if .Values.config.passwordAuthEnabled }}
- name: PASSWORD_AUTH_ENABLED
value: {{ .Values.config.passwordAuthEnabled | quote }}
{{- end }}
{{- if .Values.database.existingSecret }}
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: {{ .Values.database.existingSecret }}
key: {{ .Values.database.usernameKey }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.database.existingSecret }}
key: {{ .Values.database.passwordKey }}
{{- if .Values.database.databaseKey }}
- name: DB_NAME
valueFrom:
secretKeyRef:
name: {{ .Values.database.existingSecret }}
key: {{ .Values.database.databaseKey }}
{{- else }}
- name: DB_NAME
value: {{ .Values.database.name | quote }}
{{- end }}
{{- if .Values.database.hostKey }}
- name: DB_HOST
valueFrom:
secretKeyRef:
name: {{ .Values.database.existingSecret }}
key: {{ .Values.database.hostKey }}
{{- else }}
- name: DB_HOST
value: {{ .Values.database.host | quote }}
{{- end }}
- name: DB_PORT
value: {{ .Values.database.port | quote }}
- name: DATABASE_URL
value: "postgres://$(DB_USERNAME):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)"
{{- else }}
- name: DATABASE_URL
value: {{ include "norish.databaseUrl" . | quote }}
{{- end }}
- name: MASTER_KEY
valueFrom:
secretKeyRef:
{{- if .Values.config.masterKey.existingSecret }}
name: {{ .Values.config.masterKey.existingSecret }}
key: {{ .Values.config.masterKey.secretKey }}
{{- else }}
name: {{ include "norish.fullname" . }}-secret
key: master-key
{{- end }}
{{- if .Values.config.auth.oidc.enabled }}
- name: OIDC_NAME
value: {{ .Values.config.auth.oidc.name | quote }}
- name: OIDC_ISSUER
value: {{ .Values.config.auth.oidc.issuer | quote }}
{{- if .Values.config.auth.oidc.wellKnown }}
- name: OIDC_WELLKNOWN
value: {{ .Values.config.auth.oidc.wellKnown | quote }}
{{- end }}
- name: OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
{{- if .Values.config.auth.oidc.existingSecret }}
name: {{ .Values.config.auth.oidc.existingSecret }}
key: {{ .Values.config.auth.oidc.clientIdKey }}
{{- else }}
name: {{ include "norish.fullname" . }}-secret
key: oidc-client-id
{{- end }}
- name: OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
{{- if .Values.config.auth.oidc.existingSecret }}
name: {{ .Values.config.auth.oidc.existingSecret }}
key: {{ .Values.config.auth.oidc.clientSecretKey }}
{{- else }}
name: {{ include "norish.fullname" . }}-secret
key: oidc-client-secret
{{- end }}
{{- end }}
{{- if .Values.config.auth.github.enabled }}
- name: GITHUB_CLIENT_ID
valueFrom:
secretKeyRef:
{{- if .Values.config.auth.github.existingSecret }}
name: {{ .Values.config.auth.github.existingSecret }}
key: {{ .Values.config.auth.github.clientIdKey }}
{{- else }}
name: {{ include "norish.fullname" . }}-secret
key: github-client-id
{{- end }}
- name: GITHUB_CLIENT_SECRET
valueFrom:
secretKeyRef:
{{- if .Values.config.auth.github.existingSecret }}
name: {{ .Values.config.auth.github.existingSecret }}
key: {{ .Values.config.auth.github.clientSecretKey }}
{{- else }}
name: {{ include "norish.fullname" . }}-secret
key: github-client-secret
{{- end }}
{{- end }}
{{- if .Values.config.auth.google.enabled }}
- name: GOOGLE_CLIENT_ID
valueFrom:
secretKeyRef:
{{- if .Values.config.auth.google.existingSecret }}
name: {{ .Values.config.auth.google.existingSecret }}
key: {{ .Values.config.auth.google.clientIdKey }}
{{- else }}
name: {{ include "norish.fullname" . }}-secret
key: google-client-id
{{- end }}
- name: GOOGLE_CLIENT_SECRET
valueFrom:
secretKeyRef:
{{- if .Values.config.auth.google.existingSecret }}
name: {{ .Values.config.auth.google.existingSecret }}
key: {{ .Values.config.auth.google.clientSecretKey }}
{{- else }}
name: {{ include "norish.fullname" . }}-secret
key: google-client-secret
{{- end }}
{{- end }}
{{- with .Values.config.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
- name: uploads
mountPath: /app/uploads
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- if .Values.chrome.enabled }}
- name: chrome-headless
image: "{{ .Values.chrome.image.repository }}:{{ .Values.chrome.image.tag }}"
imagePullPolicy: {{ .Values.chrome.image.pullPolicy }}
securityContext:
{{- toYaml .Values.chrome.securityContext | nindent 12 }}
ports:
- name: chrome
containerPort: {{ .Values.chrome.port }}
protocol: TCP
command:
- chromium-browser
args:
- "--no-sandbox"
- "--disable-gpu"
- "--disable-dev-shm-usage"
- "--remote-debugging-address=0.0.0.0"
- "--remote-debugging-port={{ .Values.chrome.port }}"
- "--headless"
resources:
{{- toYaml .Values.chrome.resources | nindent 12 }}
{{- end }}
volumes:
{{- if .Values.persistence.enabled }}
- name: uploads
persistentVolumeClaim:
{{- if .Values.persistence.existingClaim }}
claimName: {{ .Values.persistence.existingClaim }}
{{- else }}
claimName: {{ include "norish.fullname" . }}-uploads
{{- end }}
{{- else }}
- name: uploads
emptyDir: {}
{{- end }}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

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

View File

@ -0,0 +1,22 @@
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "norish.fullname" . }}-uploads
labels:
{{- include "norish.labels" . | nindent 4 }}
app.kubernetes.io/component: app
{{- with .Values.persistence.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
accessModes:
- {{ .Values.persistence.accessMode }}
{{- if .Values.persistence.storageClass }}
storageClassName: {{ .Values.persistence.storageClass }}
{{- end }}
resources:
requests:
storage: {{ .Values.persistence.size }}
{{- end }}

View File

@ -0,0 +1,32 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ include "norish.fullname" . }}-secret
labels:
{{- include "norish.labels" . | nindent 4 }}
type: Opaque
stringData:
{{- if not .Values.config.masterKey.existingSecret }}
master-key: {{ .Values.config.masterKey.value | required "config.masterKey.value is required when config.masterKey.existingSecret is not set" | quote }}
{{- end }}
{{- if not .Values.database.existingSecret }}
database-url: {{ include "norish.databaseUrl" . | quote }}
{{- end }}
{{- if .Values.config.auth.oidc.enabled }}
{{- if not .Values.config.auth.oidc.existingSecret }}
oidc-client-id: {{ .Values.config.auth.oidc.clientId | quote }}
oidc-client-secret: {{ .Values.config.auth.oidc.clientSecret | quote }}
{{- end }}
{{- end }}
{{- if .Values.config.auth.github.enabled }}
{{- if not .Values.config.auth.github.existingSecret }}
github-client-id: {{ .Values.config.auth.github.clientId | quote }}
github-client-secret: {{ .Values.config.auth.github.clientSecret | quote }}
{{- end }}
{{- end }}
{{- if .Values.config.auth.google.enabled }}
{{- if not .Values.config.auth.google.existingSecret }}
google-client-id: {{ .Values.config.auth.google.clientId | quote }}
google-client-secret: {{ .Values.config.auth.google.clientSecret | quote }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,20 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "norish.fullname" . }}
labels:
{{- include "norish.labels" . | nindent 4 }}
app.kubernetes.io/component: app
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "norish.selectorLabels" . | nindent 4 }}

241
charts/norish/values.yaml Normal file
View File

@ -0,0 +1,241 @@
## Global settings
nameOverride: ""
fullnameOverride: ""
## Image settings
image:
repository: norishapp/norish
tag: "v0.13.6-beta"
pullPolicy: IfNotPresent
imagePullSecrets: []
## Deployment settings
replicaCount: 1
revisionHistoryLimit: 3
# Pod security settings
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containerSecurityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
## Pod scheduling
nodeSelector: {}
tolerations: []
affinity: {}
## Pod annotations
podAnnotations: {}
## Service settings
service:
type: ClusterIP
port: 3000
annotations: {}
## Ingress settings
ingress:
enabled: false
className: ""
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
hosts:
- host: norish.domain.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- norish.domain.com
# Optional: specify the name of an existing TLS secret
# secretName: "existing-tls-secret"
## Persistence settings
persistence:
enabled: true
# Use an existing PVC instead of creating a new one
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 5Gi
annotations: {}
# Extra volume mounts
extraVolumeMounts: []
# Extra volumes
extraVolumes: []
## Resource limits and requests
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 500m
# memory: 512Mi
# requests:
# cpu: 100m
# memory: 128Mi
## Application health checks
probes:
startup:
enabled: true
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30
successThreshold: 1
path: /
liveness:
enabled: true
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
successThreshold: 1
path: /
readiness:
enabled: true
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
successThreshold: 1
path: /
## Application configuration
config:
# Application URL (required)
# This should match your ingress hostname
authUrl: "http://norish.domain.com"
# Extra environment variables
# Example:
# extraEnv:
# - name: MY_CUSTOM_VAR
# value: "my-value"
# - name: SECRET_VAR
# valueFrom:
# secretKeyRef:
# name: my-secret
# key: secret-key
extraEnv: []
# Master encryption key (required)
# Generate with: openssl rand -base64 32
# For production, use an existing Kubernetes Secret
masterKey:
existingSecret: "" # Name of existing Kubernetes secret
secretKey: "master-key" # Key in the secret where master key is stored
value: "" # Only used if existingSecret is not set (must be 32-byte base64)
# Optional configuration
# Log level: trace, debug, info, warn, error, fatal
# Defaults to info in production, debug in development
logLevel: ""
# Additional trusted origins (comma-separated)
# Useful when behind a proxy or using multiple domains
# Example: "http://192.168.1.100:3000,https://norish.example.com"
trustedOrigins: ""
# Enable/disable password authentication
# Defaults to false if OIDC or OAuth is configured, true otherwise
passwordAuthEnabled: ""
# Authentication provider configuration
# Configure ONE provider for initial admin account creation
# After first login, manage additional providers via Settings → Admin
auth:
# OIDC/OAuth2 provider
oidc:
enabled: false
name: "MyAuth"
issuer: ""
clientId: ""
clientSecret: ""
# Optional: OIDC well-known configuration URL
# By default derived from issuer by appending /.well-known/openid-configuration
wellKnown: ""
# Use existing secret for OIDC credentials
existingSecret: ""
clientIdKey: "oidc-client-id"
clientSecretKey: "oidc-client-secret"
# GitHub OAuth
github:
enabled: false
clientId: ""
clientSecret: ""
# Use existing secret for GitHub credentials
existingSecret: ""
clientIdKey: "github-client-id"
clientSecretKey: "github-client-secret"
# Google OAuth
google:
enabled: false
clientId: ""
clientSecret: ""
# Use existing secret for Google credentials
existingSecret: ""
clientIdKey: "google-client-id"
clientSecretKey: "google-client-secret"
## External PostgreSQL database configuration (REQUIRED)
## Norish requires a central PostgreSQL database
## You must have a PostgreSQL server available before deploying this chart
database:
# Database connection details
host: "" # Required: PostgreSQL server hostname
port: 5432
name: norish
username: postgres
password: ""
# Use existing secret for database credentials (recommended for production)
existingSecret: "" # Name of existing Kubernetes secret
usernameKey: "username" # Key in the secret for database username
passwordKey: "password" # Key in the secret for database password
databaseKey: "database" # Key in the secret for database name (optional)
hostKey: "" # Key in the secret for database host (optional)
## Chrome Headless configuration (REQUIRED)
## Required for improved recipe parsing and scraping
chrome:
enabled: true
image:
repository: zenika/alpine-chrome
tag: "latest"
pullPolicy: IfNotPresent
# Chrome port for remote debugging
port: 9222
# Chrome security context - requires specific capabilities
securityContext:
runAsNonRoot: false
runAsUser: 0
capabilities:
add:
- SYS_ADMIN
# Chrome resource limits
resources: {}
# limits:
# cpu: 500m
# memory: 512Mi
# requests:
# cpu: 100m
# memory: 256Mi

View File

@ -0,0 +1,18 @@
apiVersion: v2
name: paperless-ngx
description: Paperless-ngx helm chart for Kubernetes
type: application
version: 0.0.3
appVersion: "latest"
maintainers:
- name: Richard Tomik
email: richard.tomik@proton.me
keywords:
- productivity
- document-management
- paperless
- paperless-ngx
- ocr
home: https://github.com/rtomik/helm-charts
sources:
- https://github.com/paperless-ngx/paperless-ngx

View File

@ -0,0 +1,92 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "paperless-ngx.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "paperless-ngx.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "paperless-ngx.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "paperless-ngx.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8000 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8000:$CONTAINER_PORT
{{- end }}
2. Application is accessible at port {{ .Values.service.port }}
3. Paperless-ngx is configured with:
- Database: PostgreSQL (external)
- Redis: External service
- OCR Language: {{ .Values.config.ocr.language }}
- Time Zone: {{ .Values.config.timeZone }}
4. External Dependencies Required:
- PostgreSQL server: {{ include "paperless-ngx.postgresql.host" . }}:{{ include "paperless-ngx.postgresql.port" . }}
- Redis server: {{ include "paperless-ngx.redis.host" . }}:{{ include "paperless-ngx.redis.port" . }}
{{- if or .Values.persistence.data.enabled .Values.persistence.media.enabled .Values.persistence.consume.enabled .Values.persistence.export.enabled }}
5. Persistent Storage:
{{- if .Values.persistence.data.enabled }}
- Data directory: {{ include "paperless-ngx.fullname" . }}-data ({{ .Values.persistence.data.size }})
{{- end }}
{{- if .Values.persistence.media.enabled }}
- Media directory: {{ include "paperless-ngx.fullname" . }}-media ({{ .Values.persistence.media.size }})
{{- end }}
{{- if .Values.persistence.consume.enabled }}
- Consume directory: {{ include "paperless-ngx.fullname" . }}-consume ({{ .Values.persistence.consume.size }})
{{- end }}
{{- if .Values.persistence.export.enabled }}
- Export directory: {{ include "paperless-ngx.fullname" . }}-export ({{ .Values.persistence.export.size }})
{{- end }}
{{- else }}
5. WARNING: No persistent storage enabled. Data will be lost when pods are restarted.
Enable persistence in values.yaml for production use.
{{- end }}
{{- if .Values.config.admin.user }}
6. Admin User: {{ .Values.config.admin.user }}
The admin user will be created automatically on first startup.
{{- else }}
6. No admin user configured. You'll need to create a superuser manually:
kubectl exec -it deployment/{{ include "paperless-ngx.fullname" . }} -- python manage.py createsuperuser
{{- end }}
{{- if or .Values.config.secretKey.existingSecret .Values.postgresql.external.existingSecret .Values.config.admin.existingSecret }}
7. Using external secrets for sensitive information:
{{- if .Values.config.secretKey.existingSecret }}
- Secret key from: {{ .Values.config.secretKey.existingSecret }}
{{- end }}
{{- if .Values.postgresql.external.existingSecret }}
- PostgreSQL password from: {{ .Values.postgresql.external.existingSecret }}
{{- end }}
{{- if .Values.config.admin.existingSecret }}
- Admin credentials from: {{ .Values.config.admin.existingSecret }}
{{- end }}
{{- else }}
7. SECURITY NOTE: For production use, it's recommended to store sensitive data in Kubernetes Secrets.
- Set config.secretKey.existingSecret to use an external secret for the secret key
- Set postgresql.external.existingSecret to use an external secret for database credentials
- Set config.admin.existingSecret to use an external secret for admin credentials
{{- end }}
{{- if .Values.config.consumer.barcodes.enabled }}
8. Barcode processing is enabled with scanner: {{ .Values.config.consumer.barcodeScanner }}
{{- end }}
{{- if .Values.config.tika.enabled }}
9. Tika integration is enabled for Office document processing
- Tika endpoint: {{ .Values.config.tika.endpoint }}
- Gotenberg endpoint: {{ .Values.config.tika.gotenbergEndpoint }}
{{- end }}
For more information about using this Helm chart and Paperless-ngx configuration,
please refer to the README.md file and the official Paperless-ngx documentation.

View File

@ -0,0 +1,348 @@
# Paperless-ngx Helm Chart
A Helm chart for deploying Paperless-ngx document management system on Kubernetes.
## Introduction
This chart deploys [Paperless-ngx](https://github.com/paperless-ngx/paperless-ngx) on a Kubernetes cluster using the Helm package manager.
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
## 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`
## 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
```
Or install directly from this repository:
```bash
$ git clone https://github.com/rtomik/helm-charts.git
$ cd helm-charts/charts/paperless-ngx
$ helm install 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.existingClaim` | Use an existing PVC for data directory | `""` |
| `persistence.data.size` | Size of data PVC | `1Gi` |
| `persistence.media.enabled` | Enable persistence for media directory | `true` |
| `persistence.media.existingClaim` | Use an existing PVC for media directory | `""` |
| `persistence.media.size` | Size of media PVC | `10Gi` |
| `persistence.consume.enabled` | Enable persistence for consume directory | `true` |
| `persistence.consume.existingClaim` | Use an existing PVC for consume directory | `""` |
| `persistence.consume.size` | Size of consume PVC | `5Gi` |
| `persistence.export.enabled` | Enable persistence for export directory | `true` |
| `persistence.export.existingClaim` | Use an existing PVC for export directory | `""` |
| `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
```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
```
### Production Installation with External Secrets
```yaml
# values-production.yaml
config:
url: "https://paperless.example.com"
allowedHosts: "paperless.example.com"
secretKey:
existingSecret: "paperless-secrets"
secretKey: "django-secret-key"
admin:
user: "admin"
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"
redis:
external:
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:
enabled: true
className: "nginx"
hosts:
- host: paperless.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: paperless-tls
hosts:
- 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:
```yaml
redis:
external:
host: "redis.example.com"
existingSecret: "redis-auth-secret"
passwordKey: "redis-password"
```
#### 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
Use the `prefix` parameter to avoid key collisions:
```yaml
# Instance 1
redis:
external:
host: "shared-redis.example.com"
password: "sharedpassword"
prefix: "paperless-prod"
# Instance 2
redis:
external:
host: "shared-redis.example.com"
password: "sharedpassword"
prefix: "paperless-staging"
```
## Security Considerations
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.
### Using Existing PVCs
The chart supports using existing PersistentVolumeClaims instead of creating new ones. This is useful for:
- Migrating from an existing Paperless-ngx deployment
- Using pre-provisioned storage with specific settings
- Sharing volumes across deployments
To use an existing PVC, specify the `existingClaim` parameter for the relevant volume:
```yaml
persistence:
data:
enabled: true
existingClaim: "my-existing-data-pvc"
media:
enabled: true
existingClaim: "my-existing-media-pvc"
export:
enabled: true
existingClaim: "" # Will create new PVC
consume:
enabled: true
existingClaim: "" # Will create new PVC
```
When `existingClaim` is specified:
- The chart will **NOT** create a new PVC
- The specified PVC must already exist in the same namespace
- `storageClass`, `size`, and `accessMode` parameters are ignored for that volume
- You can mix existing and new PVCs (some volumes with `existingClaim`, others without)
## Uninstalling the Chart
To uninstall/delete the `paperless-ngx` deployment:
```bash
helm uninstall paperless-ngx
```
The command removes all the Kubernetes components associated with the chart and deletes the release.
## 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.
## Links
- [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)

View File

@ -0,0 +1,109 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "paperless-ngx.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "paperless-ngx.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s" $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "paperless-ngx.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "paperless-ngx.labels" -}}
helm.sh/chart: {{ include "paperless-ngx.chart" . }}
{{ include "paperless-ngx.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "paperless-ngx.selectorLabels" -}}
app.kubernetes.io/name: {{ include "paperless-ngx.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
PostgreSQL host
*/}}
{{- define "paperless-ngx.postgresql.host" -}}
{{- if .Values.postgresql.external.enabled }}
{{- .Values.postgresql.external.host }}
{{- else }}
{{- printf "%s-postgresql" (include "paperless-ngx.fullname" .) }}
{{- end }}
{{- end }}
{{/*
PostgreSQL port
*/}}
{{- define "paperless-ngx.postgresql.port" -}}
{{- if .Values.postgresql.external.enabled }}
{{- .Values.postgresql.external.port | toString }}
{{- else }}
{{- "5432" }}
{{- end }}
{{- end }}
{{/*
Redis host
*/}}
{{- define "paperless-ngx.redis.host" -}}
{{- if .Values.redis.external.enabled }}
{{- .Values.redis.external.host }}
{{- else }}
{{- printf "%s-redis" (include "paperless-ngx.fullname" .) }}
{{- end }}
{{- end }}
{{/*
Redis port
*/}}
{{- define "paperless-ngx.redis.port" -}}
{{- if .Values.redis.external.enabled }}
{{- .Values.redis.external.port | toString }}
{{- else }}
{{- "6379" }}
{{- end }}
{{- end }}
{{/*
Redis URL
Constructs the Redis URL with optional authentication.
Format: redis://[username]:[password]@host:port/database
*/}}
{{- define "paperless-ngx.redis.url" -}}
{{- $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 }}
{{- 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 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,13 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "paperless-ngx.fullname" . }}-configmap
labels:
{{- include "paperless-ngx.labels" . | nindent 4 }}
data:
# Additional configuration files can be added here if needed
# Most Paperless-ngx configuration is handled via environment variables
README.txt: |
This ConfigMap can be used to store additional configuration files
for Paperless-ngx if needed. The main configuration is handled via
environment variables in the deployment.

View File

@ -0,0 +1,370 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "paperless-ngx.fullname" . }}
labels:
{{- include "paperless-ngx.labels" . | nindent 4 }}
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
spec:
replicas: {{ .Values.replicaCount }}
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
selector:
matchLabels:
{{- include "paperless-ngx.selectorLabels" . | nindent 6 }}
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
{{- include "paperless-ngx.selectorLabels" . | nindent 8 }}
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
{{- if .Values.probes.liveness.enabled }}
livenessProbe:
httpGet:
path: {{ .Values.probes.liveness.path }}
port: http
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
successThreshold: {{ .Values.probes.liveness.successThreshold }}
{{- end }}
{{- if .Values.probes.readiness.enabled }}
readinessProbe:
httpGet:
path: {{ .Values.probes.readiness.path }}
port: http
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
successThreshold: {{ .Values.probes.readiness.successThreshold }}
{{- end }}
env:
# Required services
- name: PAPERLESS_REDIS
value: {{ include "paperless-ngx.redis.url" . | quote }}
{{- 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
value: {{ include "paperless-ngx.postgresql.port" . | quote }}
- name: PAPERLESS_DBNAME
value: {{ .Values.postgresql.external.database | quote }}
- name: PAPERLESS_DBUSER
value: {{ .Values.postgresql.external.username | quote }}
# Database password from secret
- name: PAPERLESS_DBPASS
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.external.existingSecret | default (printf "%s-secrets" (include "paperless-ngx.fullname" .)) }}
key: {{ .Values.postgresql.external.passwordKey | default "postgresql-password" }}
# Security
- name: PAPERLESS_SECRET_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.config.secretKey.existingSecret | default (printf "%s-secrets" (include "paperless-ngx.fullname" .)) }}
key: {{ .Values.config.secretKey.secretKey | default "secret-key" }}
# Basic configuration
{{- if .Values.config.url }}
- name: PAPERLESS_URL
value: {{ .Values.config.url | quote }}
{{- end }}
- name: PAPERLESS_ALLOWED_HOSTS
value: {{ .Values.config.allowedHosts | quote }}
{{- if .Values.config.csrfTrustedOrigins }}
- name: PAPERLESS_CSRF_TRUSTED_ORIGINS
value: {{ .Values.config.csrfTrustedOrigins | quote }}
{{- end }}
- name: PAPERLESS_CORS_ALLOWED_HOSTS
value: {{ .Values.config.corsAllowedHosts | quote }}
{{- if .Values.config.forceScriptName }}
- name: PAPERLESS_FORCE_SCRIPT_NAME
value: {{ .Values.config.forceScriptName | quote }}
{{- end }}
# Paths
- name: PAPERLESS_DATA_DIR
value: "/usr/src/paperless/data"
- name: PAPERLESS_MEDIA_ROOT
value: "/usr/src/paperless/media"
- name: PAPERLESS_CONSUMPTION_DIR
value: "/usr/src/paperless/consume"
# Docker/User settings (s6-overlay compatible)
- name: USERMAP_UID
value: "1000"
- name: USERMAP_GID
value: "1000"
# OCR settings
- name: PAPERLESS_OCR_LANGUAGE
value: {{ .Values.config.ocr.language | quote }}
- name: PAPERLESS_OCR_MODE
value: {{ .Values.config.ocr.mode | quote }}
- name: PAPERLESS_OCR_SKIP_ARCHIVE_FILE
value: {{ .Values.config.ocr.skipArchiveFile | quote }}
- name: PAPERLESS_OCR_CLEAN
value: {{ .Values.config.ocr.clean | quote }}
- name: PAPERLESS_OCR_DESKEW
value: {{ .Values.config.ocr.deskew | quote }}
- name: PAPERLESS_OCR_ROTATE_PAGES
value: {{ .Values.config.ocr.rotatePages | quote }}
- name: PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD
value: {{ .Values.config.ocr.rotatePagesThreshold | quote }}
- name: PAPERLESS_OCR_OUTPUT_TYPE
value: {{ .Values.config.ocr.outputType | quote }}
{{- if ne (.Values.config.ocr.pages | int) 0 }}
- name: PAPERLESS_OCR_PAGES
value: {{ .Values.config.ocr.pages | quote }}
{{- end }}
{{- if ne (.Values.config.ocr.imageDpi | int) 0 }}
- name: PAPERLESS_OCR_IMAGE_DPI
value: {{ .Values.config.ocr.imageDpi | quote }}
{{- end }}
{{- if ne (.Values.config.ocr.maxImagePixels | int) 0 }}
- name: PAPERLESS_OCR_MAX_IMAGE_PIXELS
value: {{ .Values.config.ocr.maxImagePixels | quote }}
{{- end }}
{{- if ne .Values.config.ocr.userArgs "{}" }}
- name: PAPERLESS_OCR_USER_ARGS
value: {{ .Values.config.ocr.userArgs | quote }}
{{- end }}
# Time and locale
- name: PAPERLESS_TIME_ZONE
value: {{ .Values.config.timeZone | quote }}
# Consumer settings
- name: PAPERLESS_CONSUMER_RECURSIVE
value: {{ .Values.config.consumer.recursive | quote }}
- name: PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS
value: {{ .Values.config.consumer.subdirsAsTags | quote }}
- name: PAPERLESS_CONSUMER_DELETE_DUPLICATES
value: {{ .Values.config.consumer.deleteDocumentDuplicates | quote }}
- name: PAPERLESS_CONSUMER_IGNORE_PATTERNS
value: {{ .Values.config.consumer.ignorePatterns | quote }}
- name: PAPERLESS_CONSUMER_BARCODE_SCANNER
value: {{ .Values.config.consumer.barcodeScanner | quote }}
# Barcode settings
{{- if .Values.config.consumer.barcodes.enabled }}
- name: PAPERLESS_CONSUMER_ENABLE_BARCODES
value: "true"
- name: PAPERLESS_CONSUMER_BARCODE_TIFF_SUPPORT
value: {{ .Values.config.consumer.barcodes.tiffSupport | quote }}
- name: PAPERLESS_CONSUMER_BARCODE_STRING
value: {{ .Values.config.consumer.barcodes.string | quote }}
- name: PAPERLESS_CONSUMER_BARCODE_RETAIN_SPLIT_PAGES
value: {{ .Values.config.consumer.barcodes.retainSplitPages | quote }}
{{- if ne (.Values.config.consumer.barcodes.upscale | float64) 0.0 }}
- name: PAPERLESS_CONSUMER_BARCODE_UPSCALE
value: {{ .Values.config.consumer.barcodes.upscale | quote }}
{{- end }}
- name: PAPERLESS_CONSUMER_BARCODE_DPI
value: {{ .Values.config.consumer.barcodes.dpi | quote }}
{{- if ne (.Values.config.consumer.barcodes.maxPages | int) 0 }}
- name: PAPERLESS_CONSUMER_BARCODE_MAX_PAGES
value: {{ .Values.config.consumer.barcodes.maxPages | quote }}
{{- end }}
{{- end }}
# ASN barcode settings
{{- if .Values.config.consumer.barcodes.asnEnabled }}
- name: PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE
value: "true"
- name: PAPERLESS_CONSUMER_ASN_BARCODE_PREFIX
value: {{ .Values.config.consumer.barcodes.asnPrefix | quote }}
{{- end }}
# Tag barcode settings
{{- if .Values.config.consumer.barcodes.tagEnabled }}
- name: PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE
value: "true"
- name: PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING
value: {{ .Values.config.consumer.barcodes.tagMapping | quote }}
{{- end }}
# Tika settings
{{- if .Values.config.tika.enabled }}
- name: PAPERLESS_TIKA_ENABLED
value: "true"
- name: PAPERLESS_TIKA_ENDPOINT
value: {{ .Values.config.tika.endpoint | quote }}
- name: PAPERLESS_TIKA_GOTENBERG_ENDPOINT
value: {{ .Values.config.tika.gotenbergEndpoint | quote }}
{{- end }}
# Admin user
{{- if .Values.config.admin.user }}
- name: PAPERLESS_ADMIN_USER
value: {{ .Values.config.admin.user | quote }}
- name: PAPERLESS_ADMIN_MAIL
value: {{ .Values.config.admin.email | quote }}
- name: PAPERLESS_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.config.admin.existingSecret | default (printf "%s-secrets" (include "paperless-ngx.fullname" .)) }}
key: {{ .Values.config.admin.passwordKey | default "admin-password" }}
{{- end }}
# Email settings
{{- if .Values.config.email.host }}
- name: PAPERLESS_EMAIL_HOST
value: {{ .Values.config.email.host | quote }}
- name: PAPERLESS_EMAIL_PORT
value: {{ .Values.config.email.port | quote }}
{{- if .Values.config.email.user }}
- name: PAPERLESS_EMAIL_HOST_USER
value: {{ .Values.config.email.user | quote }}
- name: PAPERLESS_EMAIL_HOST_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.config.email.existingSecret | default (printf "%s-secrets" (include "paperless-ngx.fullname" .)) }}
key: {{ .Values.config.email.passwordKey | default "email-password" }}
{{- end }}
{{- if .Values.config.email.from }}
- name: PAPERLESS_EMAIL_FROM
value: {{ .Values.config.email.from | quote }}
{{- end }}
- name: PAPERLESS_EMAIL_USE_TLS
value: {{ .Values.config.email.useTls | quote }}
- name: PAPERLESS_EMAIL_USE_SSL
value: {{ .Values.config.email.useSsl | quote }}
{{- end }}
# Task processing
- name: PAPERLESS_TASK_WORKERS
value: {{ .Values.config.taskWorkers | quote }}
- name: PAPERLESS_THREADS_PER_WORKER
value: {{ .Values.config.threadsPerWorker | quote }}
- name: PAPERLESS_WORKER_TIMEOUT
value: {{ .Values.config.workerTimeout | quote }}
# Advanced settings
- name: PAPERLESS_ENABLE_NLTK
value: {{ .Values.config.enableNltk | quote }}
{{- if .Values.config.filenameFormat }}
- name: PAPERLESS_FILENAME_FORMAT
value: {{ .Values.config.filenameFormat | quote }}
- name: PAPERLESS_FILENAME_FORMAT_REMOVE_NONE
value: {{ .Values.config.filenameFormatRemoveNone | quote }}
{{- end }}
{{- if ne (.Values.config.convertMemoryLimit | int) 0 }}
- name: PAPERLESS_CONVERT_MEMORY_LIMIT
value: {{ .Values.config.convertMemoryLimit | quote }}
{{- end }}
{{- if .Values.config.convertTmpDir }}
- name: PAPERLESS_CONVERT_TMPDIR
value: {{ .Values.config.convertTmpDir | quote }}
{{- end }}
{{- if ne (.Values.config.maxImagePixels | int) 0 }}
- name: PAPERLESS_MAX_IMAGE_PIXELS
value: {{ .Values.config.maxImagePixels | quote }}
{{- end }}
# Custom environment variables
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- with .Values.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.extraEnvFrom }}
envFrom:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
- name: data
mountPath: /usr/src/paperless/data
- name: media
mountPath: /usr/src/paperless/media
- name: export
mountPath: /usr/src/paperless/export
- name: consume
mountPath: /usr/src/paperless/consume
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
{{- if .Values.persistence.data.enabled }}
- name: data
persistentVolumeClaim:
claimName: {{ if .Values.persistence.data.existingClaim }}{{ .Values.persistence.data.existingClaim }}{{ else }}{{ include "paperless-ngx.fullname" . }}-data{{ end }}
{{- else }}
- name: data
emptyDir: {}
{{- end }}
{{- if .Values.persistence.media.enabled }}
- name: media
persistentVolumeClaim:
claimName: {{ if .Values.persistence.media.existingClaim }}{{ .Values.persistence.media.existingClaim }}{{ else }}{{ include "paperless-ngx.fullname" . }}-media{{ end }}
{{- else }}
- name: media
emptyDir: {}
{{- end }}
{{- if .Values.persistence.export.enabled }}
- name: export
persistentVolumeClaim:
claimName: {{ if .Values.persistence.export.existingClaim }}{{ .Values.persistence.export.existingClaim }}{{ else }}{{ include "paperless-ngx.fullname" . }}-export{{ end }}
{{- else }}
- name: export
emptyDir: {}
{{- end }}
{{- if .Values.persistence.consume.enabled }}
- name: consume
persistentVolumeClaim:
claimName: {{ if .Values.persistence.consume.existingClaim }}{{ .Values.persistence.consume.existingClaim }}{{ else }}{{ include "paperless-ngx.fullname" . }}-consume{{ end }}
{{- else }}
- name: consume
emptyDir: {}
{{- end }}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

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

View File

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

View File

@ -0,0 +1,44 @@
{{- $needsSecret := false -}}
{{- if not .Values.config.secretKey.existingSecret -}}
{{- $needsSecret = true -}}
{{- end -}}
{{- if not .Values.postgresql.external.existingSecret -}}
{{- $needsSecret = true -}}
{{- end -}}
{{- if and .Values.redis.external.password (not .Values.redis.external.existingSecret) -}}
{{- $needsSecret = true -}}
{{- end -}}
{{- if and .Values.config.admin.user (not .Values.config.admin.existingSecret) -}}
{{- $needsSecret = true -}}
{{- end -}}
{{- if and .Values.config.email.host .Values.config.email.user (not .Values.config.email.existingSecret) -}}
{{- $needsSecret = true -}}
{{- end -}}
{{- if $needsSecret }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "paperless-ngx.fullname" . }}-secrets
labels:
{{- include "paperless-ngx.labels" . | nindent 4 }}
type: Opaque
data:
{{- if not .Values.config.secretKey.existingSecret }}
{{ .Values.config.secretKey.secretKey | default "secret-key" }}: {{ .Values.config.secretKey.value | default "change-me-paperless-secret-key-at-least-32-characters-long" | b64enc }}
{{- end }}
{{- if not .Values.postgresql.external.existingSecret }}
{{ .Values.postgresql.external.passwordKey | default "postgresql-password" }}: {{ .Values.postgresql.external.password | default "paperless" | b64enc }}
{{- end }}
{{- if and .Values.redis.external.password (not .Values.redis.external.existingSecret) }}
{{ .Values.redis.external.passwordKey | default "redis-password" }}: {{ .Values.redis.external.password | 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 }}
{{ .Values.config.admin.passwordKey | default "admin-password" }}: {{ .Values.config.admin.password | default "changeme" | b64enc }}
{{- end }}
{{- if and .Values.config.email.host .Values.config.email.user (not .Values.config.email.existingSecret) }}
{{ .Values.config.email.userKey | default "email-user" }}: {{ .Values.config.email.user | b64enc }}
{{ .Values.config.email.passwordKey | default "email-password" }}: {{ .Values.config.email.password | default "" | b64enc }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "paperless-ngx.fullname" . }}
labels:
{{- include "paperless-ngx.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "paperless-ngx.selectorLabels" . | nindent 4 }}

View File

@ -0,0 +1,298 @@
## Global settings
nameOverride: ""
fullnameOverride: ""
## Image settings
image:
repository: ghcr.io/paperless-ngx/paperless-ngx
tag: "2.18.4"
pullPolicy: IfNotPresent
## Deployment settings
replicaCount: 1
revisionHistoryLimit: 3
# Pod security settings
# Note: Paperless-ngx uses s6-overlay which requires root access during initialization
# The container will drop privileges after setup
podSecurityContext:
runAsNonRoot: false
runAsUser: 0
fsGroup: 1000
containerSecurityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
add:
- CHOWN
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
## Pod scheduling
nodeSelector: {}
tolerations: []
affinity: {}
## Service settings
service:
type: ClusterIP
port: 8000
## Ingress settings
ingress:
enabled: false
className: ""
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
hosts:
- host: paperless.domain.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- paperless.domain.com
# Optional: specify the name of an existing TLS secret
# secretName: "existing-tls-secret"
## Persistence settings
persistence:
# Paperless data directory (search index, classification model, etc.)
data:
enabled: true
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 1Gi
annotations: {}
# Paperless media directory (documents and thumbnails)
media:
enabled: true
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 10Gi
annotations: {}
# Export directory (for exporting documents)
export:
enabled: true
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 1Gi
annotations: {}
# Consume directory (for importing documents)
consume:
enabled: true
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 5Gi
annotations: {}
# Extra volume mounts
extraVolumeMounts: []
# Extra volumes
extraVolumes: []
## Resource limits and requests
# resources:
# limits:
# cpu: 1000m
# memory: 1Gi
# requests:
# cpu: 200m
# memory: 512Mi
## Application health checks
probes:
liveness:
enabled: true
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
successThreshold: 1
path: /
readiness:
enabled: true
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
successThreshold: 1
path: /
## Autoscaling configuration
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
## External Dependencies Configuration
## These should point to external PostgreSQL and Redis services
# External PostgreSQL database configuration
postgresql:
# External PostgreSQL connection details
external:
enabled: true
host: "postgresql.default.svc.cluster.local"
port: 5432
database: "paperless"
username: "paperless"
# Use existingSecret for credentials
existingSecret: ""
passwordKey: "postgresql-password"
# Or set password directly (not recommended for production)
password: ""
# External Redis configuration
redis:
external:
enabled: true
host: "redis.default.svc.cluster.local"
port: 6379
database: 0
# Authentication (leave empty if Redis has no auth)
username: "" # Optional: Redis username (Redis 6.0+)
# Use existingSecret for credentials if Redis has auth
existingSecret: ""
passwordKey: "redis-password"
# Or set password directly (leave empty if no auth)
password: ""
# Optional: Prefix for Redis keys and channels
# Useful for sharing one Redis server among multiple Paperless instances
prefix: ""
## Paperless-ngx Configuration
config:
# Basic server configuration
url: "" # Set to your external URL, e.g., https://paperless.domain.com
allowedHosts: "*" # Comma-separated list of allowed hosts
csrfTrustedOrigins: "" # Comma-separated list of trusted origins
corsAllowedHosts: "http://localhost:8000"
forceScriptName: "" # For hosting under subpath, e.g., /paperless
# Security settings
secretKey:
# Use existingSecret for production
existingSecret: ""
secretKey: "secret-key"
# Or set directly (not recommended for production)
value: ""
# OCR Configuration
ocr:
language: "eng" # OCR language (3-letter code)
mode: "skip" # skip, redo, or force
skipArchiveFile: "never" # never, with_text, always
clean: "clean" # clean, clean-final, none
deskew: true
rotatePages: true
rotatePagesThreshold: 12
outputType: "pdfa"
pages: 0 # 0 = all pages
imageDpi: 0 # 0 = auto
maxImagePixels: 0 # 0 = use Pillow default
userArgs: "{}" # JSON string of additional OCRmyPDF arguments
# Time and locale settings
timeZone: "UTC"
# Consumer settings
consumer:
recursive: false
subdirsAsTags: false
deleteDocumentDuplicates: false
ignorePatterns: '[".DS_Store", ".DS_STORE", "._*", ".stfolder/*", ".stversions/*", ".localized/*", "desktop.ini", "@eaDir/*", "Thumbs.db"]'
barcodeScanner: "PYZBAR"
# Barcode processing
barcodes:
enabled: false
tiffSupport: false
string: "PATCHT"
retainSplitPages: false
upscale: 0.0
dpi: 300
maxPages: 0
# ASN barcode settings
asnEnabled: false
asnPrefix: "ASN"
# Tag barcode settings
tagEnabled: false
tagMapping: '{"TAG:(.*)": "\\g<1>"}'
# Optional Tika settings (for Office documents)
tika:
enabled: false
endpoint: "http://tika:9998"
gotenbergEndpoint: "http://gotenberg:3000"
# Admin user creation (optional)
admin:
user: "" # Set to create admin user on startup
password: "" # Required if admin.user is set
email: "root@localhost"
# Use existingSecret for credentials
existingSecret: ""
userKey: "admin-user"
passwordKey: "admin-password"
# Email configuration (optional)
email:
host: ""
port: 25
user: ""
password: ""
from: ""
useTls: false
useSsl: false
# Use existingSecret for credentials
existingSecret: ""
userKey: "email-user"
passwordKey: "email-password"
# Logging
logging:
dir: "" # Uses PAPERLESS_DATA_DIR/log/ if empty
# Task processing
taskWorkers: 1
threadsPerWorker: 1
workerTimeout: 1800
# Advanced settings
filenameFormat: ""
filenameFormatRemoveNone: false
enableNltk: true
convertMemoryLimit: 0
convertTmpDir: ""
maxImagePixels: 0
# Environment variables
env: []
# Example additional env vars:
# - name: PAPERLESS_ENABLE_HTTP_REMOTE_USER
# value: "false"
# Extra environment variables from secrets
extraEnvFrom: []
# - secretRef:
# name: paperless-extra-secrets
# Extra environment variables (for advanced use cases)
extraEnv: []

View File

@ -2,7 +2,7 @@ apiVersion: v2
name: qbittorrent-vpn
description: qBittorrent with Gluetun VPN sidecar for Kubernetes
type: application
version: 0.0.1
version: 0.0.2
appVersion: 5.1.0
maintainers:
- name: Richard Tomik

View File

@ -222,6 +222,45 @@ gluetun:
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:
```yaml
sidecars:
- name: natmap
image: ghcr.io/muink/natmap:latest
imagePullPolicy: IfNotPresent
env:
- name: GATEWAY
value: "10.2.0.1"
- name: INTERFACE
value: "tun0"
- name: INTERVAL
value: "30"
volumeMounts:
- name: config
mountPath: /config
subPath: natmap
```
**Common Use Cases:**
1. **NATMap**: Automatically update port forwarding configurations
2. **Monitoring**: Add monitoring agents or exporters
3. **Custom Scripts**: Run periodic maintenance or update tasks
**Sharing Volumes:**
Sidecars can access the same volumes as the main containers:
- `config`: qBittorrent configuration volume
- `downloads`: Downloads volume
- `gluetun-config`: Gluetun configuration volume (if enabled)
For the full Kubernetes container specification reference, see the [Kubernetes documentation](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#container-v1-core).
## Troubleshooting
### VPN Connection Issues

View File

@ -255,7 +255,11 @@ spec:
resources:
{{- toYaml .Values.qbittorrent.resources | nindent 12 }}
{{- with .Values.sidecars }}
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
# Create /dev/net/tun as a device
- name: tun

View File

@ -225,4 +225,21 @@ extraVolumes: []
# Temporary options for development/debugging
hostNetwork: false
initContainers: []
initContainers: []
# Additional sidecar containers
# This allows you to add custom sidecar containers to the pod
# Each sidecar is specified using standard Kubernetes container spec
# Example: Add NATMap for port forwarding with VPN
# sidecars:
# - name: natmap
# image: ghcr.io/muink/natmap:latest
# env:
# - name: GATEWAY
# value: "10.2.0.1"
# - name: INTERFACE
# value: "tun0"
# volumeMounts:
# - name: config
# mountPath: /config
sidecars: []

18
charts/tandoor/Chart.yaml Normal file
View File

@ -0,0 +1,18 @@
apiVersion: v2
name: tandoor
description: Tandoor Recipes - A recipe management application for Kubernetes
type: application
version: 0.0.1
appVersion: "2.3.5"
maintainers:
- name: Richard Tomik
email: no@m.com
keywords:
- recipes
- cooking
- meal-planning
- tandoor
- food
home: https://github.com/rtomik/helm-charts
sources:
- https://github.com/TandoorRecipes/recipes

427
charts/tandoor/readme.md Normal file
View File

@ -0,0 +1,427 @@
# Tandoor Recipes Helm Chart
A Helm chart for deploying [Tandoor Recipes](https://github.com/TandoorRecipes/recipes) on Kubernetes.
Tandoor is a recipe management application that allows you to manage your recipes, plan meals, and create shopping lists.
Source code can be found here:
- 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)
## 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
```
## Usage Examples
### Minimal Configuration
```yaml
postgresql:
host: "postgresql.database.svc.cluster.local"
database: "tandoor"
username: "tandoor"
password: "your-secure-password"
config:
secretKey:
value: "your-secret-key-at-least-50-characters-long-for-security-purposes"
```
### Production Configuration
```yaml
postgresql:
host: "postgresql.database.svc.cluster.local"
database: "tandoor"
username: "tandoor"
existingSecret: "tandoor-db-secret"
passwordKey: "password"
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"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
hosts:
- host: tandoor.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- tandoor.example.com
secretName: tandoor-tls
persistence:
staticfiles:
enabled: true
# existingClaim: "my-existing-pvc"
storageClass: "longhorn"
size: 2Gi
mediafiles:
enabled: true
storageClass: "longhorn"
size: 10Gi
resources:
limits:
cpu: 1000m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
```
## Configuration
All configuration options are based on the official Tandoor documentation:
https://docs.tandoor.dev/system/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` | 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` |
### 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` |
| `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` |
### S3/Object Storage Configuration
| Name | Description | Value |
|------|-------------|-------|
| `config.s3.enabled` | Enable S3 storage for media files | `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.querystringExpire` | Signed URL expiration (seconds) | `3600` |
| `config.s3.existingSecret` | Existing secret for S3 credentials | `""` |
### AI Features
| Name | Description | Value |
|------|-------------|-------|
| `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` |
### External Services
| Name | Description | Value |
|------|-------------|-------|
| `config.fdcApiKey` | Food Data Central API key | `DEMO_KEY` |
| `config.disableExternalConnectors` | Disable all external connectors | `false` |
| `config.externalConnectorsQueueSize` | External connectors queue size | `100` |
### Rate Limiting
| Name | Description | Value |
|------|-------------|-------|
| `config.ratelimitUrlImportRequests` | Rate limit for URL imports | `""` |
| `config.drfThrottleRecipeUrlImport` | DRF throttle for recipe URL import | `60/hour` |
### Space Defaults
| Name | Description | Value |
|------|-------------|-------|
| `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.fractionPrefDefault` | Default fraction display | `false` |
| `config.commentPrefDefault` | Enable comments by default | `true` |
| `config.stickyNavPrefDefault` | Sticky navbar by default | `true` |
| `config.maxOwnedSpacesPrefDefault` | Max spaces per user | `100` |
### Cosmetic Configuration
| Name | Description | Value |
|------|-------------|-------|
| `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 |
|------|-------------|-------|
| `config.termsUrl` | Terms of service URL | `""` |
| `config.privacyUrl` | Privacy policy URL | `""` |
| `config.imprintUrl` | Legal imprint URL | `""` |
### hCaptcha Configuration
| Name | Description | Value |
|------|-------------|-------|
| `config.hcaptcha.siteKey` | hCaptcha site key | `""` |
| `config.hcaptcha.secret` | hCaptcha secret key | `""` |
| `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` |
### Pod Security Context
| 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` |
### Container Security Context
| Name | Description | Value |
|------|-------------|-------|
| `containerSecurityContext.allowPrivilegeEscalation` | Allow privilege escalation | `false` |
| `containerSecurityContext.readOnlyRootFilesystem` | Read-only root filesystem | `false` |
### 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` |
### Probes Configuration
| 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` |
### 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 | `{}` |
## Uninstalling the Chart
```bash
helm uninstall tandoor
```
**Note:** PVCs are not automatically deleted. To remove them:
```bash
kubectl delete pvc -l app.kubernetes.io/name=tandoor
```
## Links
- [Tandoor Recipes GitHub](https://github.com/TandoorRecipes/recipes)
- [Tandoor Documentation](https://docs.tandoor.dev/)
- [Configuration Reference](https://docs.tandoor.dev/system/configuration/)

View File

@ -0,0 +1,43 @@
Tandoor Recipes has been deployed successfully!
{{- if .Values.ingress.enabled }}
Access Tandoor at:
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
Get the application URL by running:
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "tandoor.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
Get the application URL by running:
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "tandoor.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
Access Tandoor by port-forwarding:
kubectl --namespace {{ .Release.Namespace }} port-forward service/{{ include "tandoor.fullname" . }} 8080:{{ .Values.service.port }}
Then visit: http://localhost:8080
{{- end }}
IMPORTANT: This chart requires an external PostgreSQL database.
Make sure your PostgreSQL database is configured and accessible at:
Host: {{ .Values.postgresql.host }}
Port: {{ .Values.postgresql.port }}
Database: {{ .Values.postgresql.database }}
For more information, visit:
- Tandoor Documentation: https://docs.tandoor.dev/
- Configuration Reference: https://docs.tandoor.dev/system/configuration/

View File

@ -0,0 +1,59 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "tandoor.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "tandoor.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s" $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "tandoor.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "tandoor.labels" -}}
helm.sh/chart: {{ include "tandoor.chart" . }}
{{ include "tandoor.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "tandoor.selectorLabels" -}}
app.kubernetes.io/name: {{ include "tandoor.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
PostgreSQL host
*/}}
{{- define "tandoor.postgresql.host" -}}
{{- .Values.postgresql.host }}
{{- end }}
{{/*
PostgreSQL port
*/}}
{{- define "tandoor.postgresql.port" -}}
{{- .Values.postgresql.port | toString }}
{{- end }}

View File

@ -0,0 +1,402 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "tandoor.fullname" . }}
labels:
{{- include "tandoor.labels" . | nindent 4 }}
annotations:
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
spec:
replicas: {{ .Values.replicaCount }}
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
selector:
matchLabels:
{{- include "tandoor.selectorLabels" . | nindent 6 }}
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
{{- include "tandoor.selectorLabels" . | nindent 8 }}
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.config.tandoorPort }}
protocol: TCP
{{- if .Values.probes.liveness.enabled }}
livenessProbe:
httpGet:
path: {{ .Values.probes.liveness.path }}
port: http
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
successThreshold: {{ .Values.probes.liveness.successThreshold }}
{{- end }}
{{- if .Values.probes.readiness.enabled }}
readinessProbe:
httpGet:
path: {{ .Values.probes.readiness.path }}
port: http
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
successThreshold: {{ .Values.probes.readiness.successThreshold }}
{{- end }}
env:
# Database configuration
- name: DB_ENGINE
value: "django.db.backends.postgresql"
- name: POSTGRES_HOST
value: {{ include "tandoor.postgresql.host" . | quote }}
- name: POSTGRES_PORT
value: {{ include "tandoor.postgresql.port" . | quote }}
- name: POSTGRES_DB
value: {{ .Values.postgresql.database | quote }}
- name: POSTGRES_USER
value: {{ .Values.postgresql.username | quote }}
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }}
key: {{ .Values.postgresql.passwordKey | default "postgresql-password" }}
# Security
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.config.secretKey.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }}
key: {{ .Values.config.secretKey.secretKey | default "secret-key" }}
- name: ALLOWED_HOSTS
value: {{ .Values.config.allowedHosts | quote }}
{{- if .Values.config.csrfTrustedOrigins }}
- name: CSRF_TRUSTED_ORIGINS
value: {{ .Values.config.csrfTrustedOrigins | quote }}
{{- end }}
- name: CORS_ALLOW_ALL_ORIGINS
value: {{ ternary "1" "0" .Values.config.corsAllowOrigins | quote }}
# Server configuration
- name: TANDOOR_PORT
value: {{ .Values.config.tandoorPort | quote }}
- name: GUNICORN_WORKERS
value: {{ .Values.config.gunicornWorkers | quote }}
- name: GUNICORN_THREADS
value: {{ .Values.config.gunicornThreads | quote }}
- name: GUNICORN_TIMEOUT
value: {{ .Values.config.gunicornTimeout | quote }}
- name: GUNICORN_MEDIA
value: {{ .Values.config.gunicornMedia | quote }}
# URL configuration
{{- if .Values.config.scriptName }}
- name: SCRIPT_NAME
value: {{ .Values.config.scriptName | quote }}
{{- end }}
# Session cookie configuration
{{- if .Values.config.sessionCookieDomain }}
- name: SESSION_COOKIE_DOMAIN
value: {{ .Values.config.sessionCookieDomain | quote }}
{{- end }}
- name: SESSION_COOKIE_NAME
value: {{ .Values.config.sessionCookieName | quote }}
# Time and locale
- name: TZ
value: {{ .Values.config.timezone | quote }}
# Feature toggles
- name: ENABLE_SIGNUP
value: {{ ternary "1" "0" .Values.config.enableSignup | quote }}
- name: ENABLE_METRICS
value: {{ ternary "1" "0" .Values.config.enableMetrics | quote }}
- name: ENABLE_PDF_EXPORT
value: {{ ternary "1" "0" .Values.config.enablePdfExport | quote }}
- name: SORT_TREE_BY_NAME
value: {{ ternary "1" "0" .Values.config.sortTreeByName | quote }}
# Social authentication
- name: SOCIAL_DEFAULT_ACCESS
value: {{ .Values.config.socialDefaultAccess | quote }}
- name: SOCIAL_DEFAULT_GROUP
value: {{ .Values.config.socialDefaultGroup | quote }}
{{- if or .Values.config.socialProviders .Values.config.oidc.enabled }}
- name: SOCIAL_PROVIDERS
value: {{ .Values.config.socialProviders | default "allauth.socialaccount.providers.openid_connect" | quote }}
{{- end }}
{{- if .Values.config.socialAccountProviders }}
- name: SOCIALACCOUNT_PROVIDERS
value: {{ .Values.config.socialAccountProviders | quote }}
{{- end }}
# OpenID Connect / OAuth configuration (e.g., Authentik, Keycloak)
# Note: For production, consider using extraEnvFrom with a secret containing SOCIALACCOUNT_PROVIDERS
{{- if .Values.config.oidc.enabled }}
{{- $clientId := .Values.config.oidc.clientId }}
{{- $clientSecret := .Values.config.oidc.clientSecret }}
- name: SOCIALACCOUNT_PROVIDERS
value: '{"openid_connect":{"APPS":[{"provider_id":"{{ .Values.config.oidc.providerId }}","name":"{{ .Values.config.oidc.providerName }}","client_id":"{{ $clientId }}","secret":"{{ $clientSecret }}","settings":{"server_url":"{{ .Values.config.oidc.serverUrl }}"}}]}}'
{{- end }}
# Remote user authentication
- name: REMOTE_USER_AUTH
value: {{ ternary "1" "0" .Values.config.remoteUserAuth | quote }}
# LDAP configuration
{{- if .Values.config.ldap.enabled }}
- name: LDAP_AUTH
value: "1"
- name: AUTH_LDAP_SERVER_URI
value: {{ .Values.config.ldap.serverUri | quote }}
{{- if .Values.config.ldap.bindDn }}
- name: AUTH_LDAP_BIND_DN
value: {{ .Values.config.ldap.bindDn | quote }}
{{- end }}
{{- if or .Values.config.ldap.bindPassword .Values.config.ldap.existingSecret }}
- name: AUTH_LDAP_BIND_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.config.ldap.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }}
key: {{ .Values.config.ldap.bindPasswordKey | default "ldap-bind-password" }}
{{- end }}
{{- if .Values.config.ldap.userSearchBaseDn }}
- name: AUTH_LDAP_USER_SEARCH_BASE_DN
value: {{ .Values.config.ldap.userSearchBaseDn | quote }}
{{- end }}
{{- if .Values.config.ldap.tlsCacertFile }}
- name: AUTH_LDAP_TLS_CACERTFILE
value: {{ .Values.config.ldap.tlsCacertFile | quote }}
{{- end }}
{{- if .Values.config.ldap.startTls }}
- name: AUTH_LDAP_START_TLS
value: "True"
{{- end }}
{{- end }}
# Email configuration
{{- if .Values.config.email.host }}
- name: EMAIL_HOST
value: {{ .Values.config.email.host | quote }}
- name: EMAIL_PORT
value: {{ .Values.config.email.port | quote }}
{{- if .Values.config.email.user }}
- name: EMAIL_HOST_USER
value: {{ .Values.config.email.user | quote }}
- name: EMAIL_HOST_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.config.email.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }}
key: {{ .Values.config.email.passwordKey | default "email-password" }}
{{- end }}
- name: EMAIL_USE_TLS
value: {{ ternary "1" "0" .Values.config.email.useTls | quote }}
- name: EMAIL_USE_SSL
value: {{ ternary "1" "0" .Values.config.email.useSsl | quote }}
- name: DEFAULT_FROM_EMAIL
value: {{ .Values.config.email.defaultFrom | quote }}
- name: ACCOUNT_EMAIL_SUBJECT_PREFIX
value: {{ .Values.config.email.accountEmailSubjectPrefix | quote }}
{{- end }}
# S3/Object storage configuration
{{- if .Values.config.s3.enabled }}
- name: S3_ACCESS_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.config.s3.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }}
key: {{ .Values.config.s3.accessKeyKey | default "s3-access-key" }}
- name: S3_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.config.s3.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }}
key: {{ .Values.config.s3.secretAccessKeyKey | default "s3-secret-access-key" }}
- name: S3_BUCKET_NAME
value: {{ .Values.config.s3.bucketName | quote }}
{{- if .Values.config.s3.regionName }}
- name: S3_REGION_NAME
value: {{ .Values.config.s3.regionName | quote }}
{{- end }}
{{- if .Values.config.s3.endpointUrl }}
- name: S3_ENDPOINT_URL
value: {{ .Values.config.s3.endpointUrl | quote }}
{{- end }}
{{- if .Values.config.s3.customDomain }}
- name: S3_CUSTOM_DOMAIN
value: {{ .Values.config.s3.customDomain | quote }}
{{- end }}
- name: S3_QUERYSTRING_AUTH
value: {{ ternary "1" "0" .Values.config.s3.querystringAuth | quote }}
- name: S3_QUERYSTRING_EXPIRE
value: {{ .Values.config.s3.querystringExpire | quote }}
{{- end }}
# AI features
{{- if .Values.config.ai.enabled }}
- name: SPACE_AI_ENABLED
value: "1"
- name: SPACE_AI_CREDITS_MONTHLY
value: {{ .Values.config.ai.creditsMonthly | quote }}
- name: AI_RATELIMIT
value: {{ .Values.config.ai.rateLimit | quote }}
{{- end }}
# Food Data Central API
- name: FDC_API_KEY
value: {{ .Values.config.fdcApiKey | quote }}
# External connectors
- name: DISABLE_EXTERNAL_CONNECTORS
value: {{ ternary "1" "0" .Values.config.disableExternalConnectors | quote }}
- name: EXTERNAL_CONNECTORS_QUEUE_SIZE
value: {{ .Values.config.externalConnectorsQueueSize | quote }}
# Rate limiting
{{- if .Values.config.ratelimitUrlImportRequests }}
- name: RATELIMIT_URL_IMPORT_REQUESTS
value: {{ .Values.config.ratelimitUrlImportRequests | quote }}
{{- end }}
- name: DRF_THROTTLE_RECIPE_URL_IMPORT
value: {{ .Values.config.drfThrottleRecipeUrlImport | quote }}
# Space defaults
- name: SPACE_DEFAULT_MAX_RECIPES
value: {{ .Values.config.spaceDefaultMaxRecipes | quote }}
- name: SPACE_DEFAULT_MAX_USERS
value: {{ .Values.config.spaceDefaultMaxUsers | quote }}
- name: SPACE_DEFAULT_MAX_FILES
value: {{ .Values.config.spaceDefaultMaxFiles | quote }}
- name: SPACE_DEFAULT_ALLOW_SHARING
value: {{ ternary "1" "0" .Values.config.spaceDefaultAllowSharing | quote }}
# User preference defaults
- name: FRACTION_PREF_DEFAULT
value: {{ ternary "1" "0" .Values.config.fractionPrefDefault | quote }}
- name: COMMENT_PREF_DEFAULT
value: {{ ternary "1" "0" .Values.config.commentPrefDefault | quote }}
- name: STICKY_NAV_PREF_DEFAULT
value: {{ ternary "1" "0" .Values.config.stickyNavPrefDefault | quote }}
- name: MAX_OWNED_SPACES_PREF_DEFAULT
value: {{ .Values.config.maxOwnedSpacesPrefDefault | quote }}
# Cosmetic
- name: UNAUTHENTICATED_THEME_FROM_SPACE
value: {{ .Values.config.unauthenticatedThemeFromSpace | quote }}
- name: FORCE_THEME_FROM_SPACE
value: {{ .Values.config.forceThemeFromSpace | quote }}
# Performance
- name: SHOPPING_MIN_AUTOSYNC_INTERVAL
value: {{ .Values.config.shoppingMinAutosyncInterval | quote }}
- name: EXPORT_FILE_CACHE_DURATION
value: {{ .Values.config.exportFileCacheDuration | quote }}
# Legal URLs
{{- if .Values.config.termsUrl }}
- name: TERMS_URL
value: {{ .Values.config.termsUrl | quote }}
{{- end }}
{{- if .Values.config.privacyUrl }}
- name: PRIVACY_URL
value: {{ .Values.config.privacyUrl | quote }}
{{- end }}
{{- if .Values.config.imprintUrl }}
- name: IMPRINT_URL
value: {{ .Values.config.imprintUrl | quote }}
{{- end }}
# hCaptcha
{{- if .Values.config.hcaptcha.siteKey }}
- name: HCAPTCHA_SITEKEY
value: {{ .Values.config.hcaptcha.siteKey | quote }}
- name: HCAPTCHA_SECRET
valueFrom:
secretKeyRef:
name: {{ .Values.config.hcaptcha.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }}
key: {{ .Values.config.hcaptcha.secretKeyKey | default "hcaptcha-secret" }}
{{- end }}
# Debugging
- name: DEBUG
value: {{ ternary "1" "0" .Values.config.debug | quote }}
- name: DEBUG_TOOLBAR
value: {{ ternary "1" "0" .Values.config.debugToolbar | quote }}
- name: SQL_DEBUG
value: {{ ternary "1" "0" .Values.config.sqlDebug | quote }}
- name: LOG_LEVEL
value: {{ .Values.config.logLevel | quote }}
- name: GUNICORN_LOG_LEVEL
value: {{ .Values.config.gunicornLogLevel | quote }}
# Custom environment variables
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- with .Values.extraEnvFrom }}
envFrom:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
- name: staticfiles
mountPath: /opt/recipes/staticfiles
- name: mediafiles
mountPath: /opt/recipes/mediafiles
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
{{- if .Values.persistence.staticfiles.enabled }}
- name: staticfiles
persistentVolumeClaim:
claimName: {{ .Values.persistence.staticfiles.existingClaim | default (printf "%s-staticfiles" (include "tandoor.fullname" .)) }}
{{- else }}
- name: staticfiles
emptyDir: {}
{{- end }}
{{- if .Values.persistence.mediafiles.enabled }}
- name: mediafiles
persistentVolumeClaim:
claimName: {{ .Values.persistence.mediafiles.existingClaim | default (printf "%s-mediafiles" (include "tandoor.fullname" .)) }}
{{- else }}
- name: mediafiles
emptyDir: {}
{{- end }}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

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

View File

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

View File

@ -0,0 +1,49 @@
{{- $needsSecret := false -}}
{{- if not .Values.config.secretKey.existingSecret -}}
{{- $needsSecret = true -}}
{{- end -}}
{{- if not .Values.postgresql.existingSecret -}}
{{- $needsSecret = true -}}
{{- end -}}
{{- if and .Values.config.ldap.enabled .Values.config.ldap.bindPassword (not .Values.config.ldap.existingSecret) -}}
{{- $needsSecret = true -}}
{{- end -}}
{{- if and .Values.config.email.host .Values.config.email.user (not .Values.config.email.existingSecret) -}}
{{- $needsSecret = true -}}
{{- end -}}
{{- if and .Values.config.s3.enabled (not .Values.config.s3.existingSecret) -}}
{{- $needsSecret = true -}}
{{- end -}}
{{- if and .Values.config.hcaptcha.siteKey .Values.config.hcaptcha.secret (not .Values.config.hcaptcha.existingSecret) -}}
{{- $needsSecret = true -}}
{{- end -}}
{{- if $needsSecret }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "tandoor.fullname" . }}-secrets
labels:
{{- include "tandoor.labels" . | nindent 4 }}
type: Opaque
data:
{{- if not .Values.config.secretKey.existingSecret }}
{{ .Values.config.secretKey.secretKey | default "secret-key" }}: {{ .Values.config.secretKey.value | default "change-me-tandoor-secret-key-at-least-50-characters-long-for-security" | b64enc }}
{{- end }}
{{- if not .Values.postgresql.existingSecret }}
{{ .Values.postgresql.passwordKey | default "postgresql-password" }}: {{ .Values.postgresql.password | default "tandoor" | b64enc }}
{{- end }}
{{- if and .Values.config.ldap.enabled .Values.config.ldap.bindPassword (not .Values.config.ldap.existingSecret) }}
{{ .Values.config.ldap.bindPasswordKey | default "ldap-bind-password" }}: {{ .Values.config.ldap.bindPassword | b64enc }}
{{- end }}
{{- if and .Values.config.email.host .Values.config.email.user (not .Values.config.email.existingSecret) }}
{{ .Values.config.email.passwordKey | default "email-password" }}: {{ .Values.config.email.password | default "" | b64enc }}
{{- end }}
{{- if and .Values.config.s3.enabled (not .Values.config.s3.existingSecret) }}
{{ .Values.config.s3.accessKeyKey | default "s3-access-key" }}: {{ .Values.config.s3.accessKey | default "" | b64enc }}
{{ .Values.config.s3.secretAccessKeyKey | default "s3-secret-access-key" }}: {{ .Values.config.s3.secretAccessKey | default "" | b64enc }}
{{- end }}
{{- if and .Values.config.hcaptcha.siteKey .Values.config.hcaptcha.secret (not .Values.config.hcaptcha.existingSecret) }}
{{ .Values.config.hcaptcha.secretKeyKey | default "hcaptcha-secret" }}: {{ .Values.config.hcaptcha.secret | b64enc }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "tandoor.fullname" . }}
labels:
{{- include "tandoor.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "tandoor.selectorLabels" . | nindent 4 }}

317
charts/tandoor/values.yaml Normal file
View File

@ -0,0 +1,317 @@
## Global settings
nameOverride: ""
fullnameOverride: ""
## Image settings
image:
repository: vabene1111/recipes
tag: "2.3.5"
pullPolicy: IfNotPresent
## Deployment settings
replicaCount: 1
revisionHistoryLimit: 3
# Pod security settings
# Note: Tandoor runs nginx internally which requires root privileges
# to write to /var/lib/nginx, /run/nginx, and /opt/recipes/http.d
podSecurityContext:
fsGroup: 0
containerSecurityContext:
runAsUser: 0
runAsGroup: 0
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
## Pod scheduling
nodeSelector: {}
tolerations: []
affinity: {}
## Service settings
service:
type: ClusterIP
port: 8080
## Ingress settings
ingress:
enabled: false
className: ""
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
# Enable these for proper HTTP to HTTPS redirect (prevents Origin: null issues)
# traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd
hosts:
- host: tandoor.domain.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- tandoor.domain.com
# Optional: specify the name of an existing TLS secret
# secretName: "existing-tls-secret"
## Persistence settings
persistence:
# Tandoor static files directory
staticfiles:
enabled: true
# Use an existing PVC instead of creating a new one
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 1Gi
annotations: {}
# Tandoor media files directory (recipe images, etc.)
mediafiles:
enabled: true
# Use an existing PVC instead of creating a new one
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 5Gi
annotations: {}
# Extra volume mounts
extraVolumeMounts: []
# Extra volumes
extraVolumes: []
## Resource limits and requests
# resources:
# limits:
# cpu: 1000m
# memory: 512Mi
# requests:
# cpu: 100m
# memory: 256Mi
## Application health checks
probes:
liveness:
enabled: true
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
successThreshold: 1
path: /
readiness:
enabled: true
initialDelaySeconds: 15
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
successThreshold: 1
path: /
## Autoscaling configuration
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
## External PostgreSQL database configuration
## This chart does NOT include PostgreSQL - you must provide an external database
postgresql:
host: "postgresql.default.svc.cluster.local"
port: 5432
database: "tandoor"
username: "tandoor"
# Use existingSecret for credentials (recommended for production)
existingSecret: ""
passwordKey: "postgresql-password"
# Or set password directly (not recommended for production)
password: ""
## Tandoor Configuration
## All settings based on official documentation: https://docs.tandoor.dev/system/configuration/
config:
# Required: Secret key for Django cryptographic operations (at least 50 characters)
secretKey:
# Use existingSecret for production
existingSecret: ""
secretKey: "secret-key"
# Or set directly (not recommended for production)
value: ""
# Security setting to prevent HTTP Host Header Attacks
allowedHosts: "*"
# Allows setting origins to allow for unsafe requests (CSRF)
csrfTrustedOrigins: ""
# Enable cross-origin resource sharing
corsAllowOrigins: false
# Time and locale settings
timezone: "UTC"
# Server configuration
tandoorPort: 8080
gunicornWorkers: 3
gunicornThreads: 2
gunicornTimeout: 30
gunicornMedia: 0
# URL configuration (for reverse proxy setups)
# URL path base for subfolder deployments
scriptName: ""
# Session cookie configuration
sessionCookieDomain: ""
sessionCookieName: "sessionid"
# Feature toggles
enableSignup: false
enableMetrics: false
enablePdfExport: false
sortTreeByName: false
# Social authentication
socialDefaultAccess: 0
socialDefaultGroup: "guest"
socialProviders: ""
# For OpenID Connect providers (like Authentik), use the socialAccountProviders field
# or set via env for complex JSON configurations
socialAccountProviders: ""
# OpenID Connect / OAuth configuration (e.g., Authentik, Keycloak, etc.)
# For simple single-provider OIDC setup, configure here.
# For complex multi-provider setups or production with secrets, use env + extraEnvFrom.
oidc:
enabled: false
# Provider ID (e.g., "authentik", "keycloak")
providerId: "authentik"
# Display name shown on login page
providerName: "Authentik"
# Client ID from your OIDC provider
clientId: ""
# Client Secret from your OIDC provider (for production, use extraEnvFrom with a secret)
clientSecret: ""
# OpenID Connect well-known configuration URL
# e.g., https://authentik.company/application/o/<application_slug>/.well-known/openid-configuration
serverUrl: ""
# Remote user authentication
remoteUserAuth: false
# LDAP authentication (optional)
ldap:
enabled: false
serverUri: ""
bindDn: ""
bindPassword: ""
bindPasswordFile: ""
userSearchBaseDn: ""
tlsCacertFile: ""
startTls: false
existingSecret: ""
bindPasswordKey: "ldap-bind-password"
# Email configuration (optional)
email:
host: ""
port: 25
user: ""
password: ""
useTls: false
useSsl: false
defaultFrom: "webmaster@localhost"
accountEmailSubjectPrefix: "[Tandoor Recipes]"
existingSecret: ""
passwordKey: "email-password"
# S3/Object storage configuration (optional)
s3:
enabled: false
accessKey: ""
secretAccessKey: ""
bucketName: ""
regionName: ""
endpointUrl: ""
customDomain: ""
querystringAuth: true
querystringExpire: 3600
existingSecret: ""
accessKeyKey: "s3-access-key"
secretAccessKeyKey: "s3-secret-access-key"
# AI features (optional)
ai:
enabled: false
creditsMonthly: 100
rateLimit: "60/hour"
# Food Data Central API key for nutrition data
fdcApiKey: "DEMO_KEY"
# External connectors
disableExternalConnectors: false
externalConnectorsQueueSize: 100
# Rate limiting
ratelimitUrlImportRequests: ""
drfThrottleRecipeUrlImport: "60/hour"
# Space defaults
spaceDefaultMaxRecipes: 0
spaceDefaultMaxUsers: 0
spaceDefaultMaxFiles: 0
spaceDefaultAllowSharing: true
# User preference defaults
fractionPrefDefault: false
commentPrefDefault: true
stickyNavPrefDefault: true
maxOwnedSpacesPrefDefault: 100
# Cosmetic
unauthenticatedThemeFromSpace: 0
forceThemeFromSpace: 0
# Performance
shoppingMinAutosyncInterval: 5
exportFileCacheDuration: 600
# Legal URLs (optional)
termsUrl: ""
privacyUrl: ""
imprintUrl: ""
# hCaptcha (optional)
hcaptcha:
siteKey: ""
secret: ""
existingSecret: ""
secretKeyKey: "hcaptcha-secret"
# Debugging (not recommended for production)
debug: false
debugToolbar: false
sqlDebug: false
logLevel: "WARNING"
gunicornLogLevel: "info"
# Environment variables (for additional configuration not covered above)
# Use this for advanced configurations or settings not exposed in config section
env: []
# Example: Custom environment variable
# - name: CUSTOM_VAR
# value: "custom-value"
#
# Example: Complex SOCIALACCOUNT_PROVIDERS for multiple OIDC providers
# - name: SOCIAL_PROVIDERS
# value: "allauth.socialaccount.providers.openid_connect"
# - name: SOCIALACCOUNT_PROVIDERS
# value: '{"openid_connect":{"APPS":[{"provider_id":"authentik","name":"Authentik","client_id":"your-client-id","secret":"your-client-secret","settings":{"server_url":"https://authentik.company/application/o/tandoor/.well-known/openid-configuration"}}]}}'
# Extra environment variables from secrets (recommended for sensitive data)
extraEnvFrom: []
# - secretRef:
# name: tandoor-extra-secrets