Compare commits

...

4 Commits

Author SHA1 Message Date
Paul Payne
d1304a2630 v2 app deployment--templating mainly in manifest now. 2025-12-31 06:53:17 +00:00
Paul Payne
8818d822cf loomio (not yet working), and new config for postgres and redis 2025-12-30 03:39:19 +00:00
Paul Payne
1b78abbdc4 claude 2025-12-30 03:38:48 +00:00
Paul Payne
a4db0d0f6a change to defaultSecrets 2025-12-30 03:38:39 +00:00
103 changed files with 1091 additions and 791 deletions

View File

@@ -26,8 +26,9 @@ description: Immich is a self-hosted photo and video backup solution that allows
version: 1.0.0 version: 1.0.0
icon: https://immich.app/assets/images/logo.png icon: https://immich.app/assets/images/logo.png
requires: requires:
- name: redis - name: pg
- name: postgres alias: db # Use a different reference name in templates
- name: redis # 'alias' and 'installedAs' default to 'name' value
defaultConfig: defaultConfig:
serverImage: ghcr.io/immich-app/immich-server:release serverImage: ghcr.io/immich-app/immich-server:release
mlImage: ghcr.io/immich-app/immich-machine-learning:release mlImage: ghcr.io/immich-app/immich-machine-learning:release
@@ -36,13 +37,21 @@ defaultConfig:
mlPort: 3003 mlPort: 3003
storage: 250Gi storage: 250Gi
cacheStorage: 10Gi cacheStorage: 10Gi
redisHostname: redis.redis.svc.cluster.local redisHostname: "{{ .apps.redis.host }}" # Can reference 'requires' app configurations
dbHostname: postgres.postgres.svc.cluster.local dbHostname: "{{ .apps.pg.host }}"
dbUsername: immich db: # Configuration can be nested
name: immich
user: immich
host: "{{ .apps.pg.host }}"
port: "{{ .apps.pg.port }}"
domain: immich.{{ .cloud.domain }} domain: immich.{{ .cloud.domain }}
defaultSecrets:
- key: password # Random value will be generated if empty
- key: dbUrl
default: "postgresql://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?pool=30" # Can reference secrets and config as long as they have been defined before this line. Reference config with {{ .app.? }} and secrets with {{ .secrets.? }}
requiredSecrets: requiredSecrets:
- apps.immich.dbPassword - db.password # References postgres app via 'db' alias
- apps.postgres.password - redis.auth # References redis app via 'redis' name (no alias)
``` ```
#### Manifest Fields #### Manifest Fields
@@ -53,11 +62,31 @@ requiredSecrets:
| `description` | Yes | Brief app description shown in listings | | `description` | Yes | Brief app description shown in listings |
| `version` | Yes | App version (follow upstream versioning) | | `version` | Yes | App version (follow upstream versioning) |
| `icon` | No | URL to app icon for UI display | | `icon` | No | URL to app icon for UI display |
| `requires` | No | List of dependency apps (e.g., `postgres`, `redis`) | | `requires` | No | List of dependency apps with optional aliases |
| `defaultConfig` | Yes | Default configuration values merged into operator's `config.yaml` | | `defaultConfig` | Yes | Default configuration values merged into operator's `config.yaml` |
| `requiredSecrets` | No | List of secrets in dotted-path format (e.g., `apps.appname.dbPassword`) | | `defaultSecrets` | No | This app's secrets (no 'default' = auto-generated) |
| `requiredSecrets` | No | List of secrets from dependency apps (format: `<app-ref>.<key>`) |
**Important:** All configuration keys referenced in templates (via `{{ .apps.appname.key }}`) must be defined in `defaultConfig` or be standard Wild Cloud variables. **Dependency Configuration:**
- Each dependency in `requires` can have:
- `name`: The actual app name to depend on
- `alias`: Optional reference name for templates (defaults to `name`)
**Manifest Template Variable Sources:**
1. Standard Wild Cloud variables: `{{ .cloud.* }}`, `{{ .cluster.* }}`, `{{ .operator.* }}`
2. App-specific variables: `{{ .app.* }}` - resolved from current app's config
3. Dependency variables: `{{ .apps.<ref>.* }}` - resolved using app reference mapping
4. App-specific secrets (in 'defaultSecrets' ONLY): `{{ secrets.* }}`
**Manifest App Reference Resolution:**
When you use `{{ .apps.<ref>.* }}` in templates:
1. System checks if `<ref>` matches any dependency's `alias` field
2. If no alias match, checks if `<ref>` matches any dependency's `name` field
3. Uses the `installedAs` value (automatically added when the app is added) to find actual app configuration in `config.yaml`
All manifest template variables must be defined in one of these locations.
**Important:** In the rest of the app templates, ALL configuration keys referenced in templates (via `{{ .key }}`) must be defined in `defaultConfig`. Only the app config is available to app templates.
### Kustomization (`kustomization.yaml`) ### Kustomization (`kustomization.yaml`)
@@ -111,21 +140,7 @@ This means individual resources can use simple, component-specific selectors lik
### Gomplate Templating ### Gomplate Templating
Resource files in this repository are **templates** that get compiled when users add apps via the web app, CLI, or API. Use gomplate syntax to reference configuration: Resource files in this repository are **templates** that get compiled when users add apps via the web app, CLI, or API. Only variables defined in the manifest file's 'defaultConfig' section are available to the resource templates. Use gomplate syntax to reference configuration:
```yaml
# Common template variables
domain: {{ .cloud.domain }} # Operator's domain
email: {{ .operator.email }} # Operator's email
image: {{ .apps.myapp.serverImage }} # App-specific config
dbHost: {{ .apps.myapp.dbHostname }} # App-specific config
```
**Template variable sources:**
1. Standard Wild Cloud variables (`{{ .cloud.* }}`, `{{ .operator.* }}`)
2. App-specific variables defined in your manifest's `defaultConfig`
All template variables must be defined in one of these locations. The compiled files are placed in the instance's directory as standard Kubernetes manifests.
### External DNS ### External DNS
@@ -133,12 +148,47 @@ Ingress resources should include external-dns annotations for automatic DNS mana
```yaml ```yaml
annotations: annotations:
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }} external-dns.alpha.kubernetes.io/target: {{ .domain }}
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false" external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
``` ```
Note: 'domain' must be defined in the app manifest's 'defaultConfig' section.
This creates a CNAME from the app subdomain to the cluster domain (e.g., `myapp.cloud.example.com``cloud.example.com`). This creates a CNAME from the app subdomain to the cluster domain (e.g., `myapp.cloud.example.com``cloud.example.com`).
## App Dependencies and Reference Mapping
### How Dependency References Work
When an app depends on other apps, the reference system allows flexibility in naming while maintaining clear relationships:
1. **Define dependencies** in your manifest with optional aliases:
```yaml
requires:
- name: postgres # Actual app to depend on
alias: db # Optional: how to reference it in templates
- name: redis # No alias means use 'redis' as reference
```
2. **At installation time**, the system:
- Prompts user to map dependencies to actual installed apps
- Sets `installedAs` field in the local app manifest to track the mapping
- Example: User might have `postgres-primary` installed, mapped to the `db` dependency
### Example: Multiple Database Instances
If a user has multiple PostgreSQL instances:
```yaml
# User's config.yaml
apps:
postgres-primary:
hostname: primary.postgres.svc.cluster.local
postgres-analytics:
hostname: analytics.postgres.svc.cluster.local
```
When adding an app that requires postgres, they can choose which instance to use, and the system tracks this in the manifest's `installedAs` field.
## Database Patterns ## Database Patterns
### Database Initialization Jobs ### Database Initialization Jobs
@@ -177,7 +227,7 @@ When apps need database URLs with embedded credentials, **always use a dedicated
key: apps.myapp.dbUrl key: apps.myapp.dbUrl
``` ```
Add `apps.myapp.dbUrl` to your manifest's `requiredSecrets`, and the system will generate the complete URL with embedded credentials automatically when the app is added. Add `apps.myapp.dbUrl` to your manifest's `defaultSecrets`, and the system will generate the complete URL with embedded credentials automatically when the app is added.
## Security Requirements ## Security Requirements
@@ -211,13 +261,16 @@ spec:
### Secrets Management ### Secrets Management
Secrets use a **full dotted-path naming convention** to prevent naming conflicts: Secrets are managed through two mechanisms: default secrets for the app itself and required secrets from dependencies.
**In manifest:** **In manifest:**
```yaml ```yaml
defaultSecrets:
key: dbPassword # This app's database password
key: apiKey # This app's API key
requiredSecrets: requiredSecrets:
- apps.myapp.dbPassword - db.password # Password from postgres dependency (aliased as 'db')
- apps.postgres.password - redis.auth # Auth from redis dependency
``` ```
**In resources:** **In resources:**
@@ -227,14 +280,26 @@ env:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: myapp-secrets name: myapp-secrets
key: apps.myapp.dbPassword # Full dotted path, not just "dbPassword" key: dbPassword # Points to the default secret
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-secrets
key: db.password # Points to the required secret
``` ```
**Secret workflow:** **Secret workflow:**
1. List secrets in manifest's `requiredSecrets` 1. Define app's own secrets in `defaultSecrets` (key, default mappings)
2. When adding an app, the system generates random values in the instance's `secrets.yaml` 2. Reference dependency secrets in `requiredSecrets` (list)
3. When deploying, the system creates a Kubernetes Secret named `<app-name>-secrets` 3. When adding an app, the system:
4. Resources reference secrets using full dotted paths - Generates random values for empty `defaultSecrets`
- Copies referenced secrets from dependencies
- Stores all in the instance's `secrets.yaml`
4. When deploying, creates a Kubernetes Secret named `<app-name>-secrets` containing:
- All `defaultSecrets` with key format: `<key>`
- All `requiredSecrets` with key format: `<app-ref>.<key>`
**Key collision handling:** If the same key exists in both `defaultSecrets` and `requiredSecrets`, the `requiredSecrets` value takes precedence. Authors should ensure their local secrets don't collide with their required secrets.
**Important:** Never commit `secrets.yaml` to Git. Templates should only reference secrets, never contain actual secret values. **Important:** Never commit `secrets.yaml` to Git. Templates should only reference secrets, never contain actual secret values.
@@ -308,9 +373,11 @@ Before submitting a new or modified app, verify:
- [ ] **Manifest** - [ ] **Manifest**
- [ ] `name` matches directory name - [ ] `name` matches directory name
- [ ] All required fields present (`name`, `description`, `version`, `defaultConfig`) - [ ] All required fields present (`name`, `description`, `version`, `defaultConfig`)
- [ ] All template variables defined in `defaultConfig` or are standard Wild Cloud variables - [ ] All template variables defined in `defaultConfig`
- [ ] Secrets use dotted-path format (e.g., `apps.appname.secretname`) - [ ] `defaultSecrets` uses maps with 'key' and 'default' attributes
- [ ] Dependencies listed in `requires` (if any) - [ ] `requiredSecrets` references use `<app-ref>.<key>` format
- [ ] Dependencies listed in `requires` with optional `alias` fields
- [ ] Manifest template references match dependency aliases or names
- [ ] **Kustomization** - [ ] **Kustomization**
- [ ] Includes standard Wild Cloud labels with `includeSelectors: true` - [ ] Includes standard Wild Cloud labels with `includeSelectors: true`
@@ -318,8 +385,6 @@ Before submitting a new or modified app, verify:
- [ ] All resource files listed under `resources:` - [ ] All resource files listed under `resources:`
- [ ] **Resources** - [ ] **Resources**
- [ ] All hardcoded values replaced with gomplate variables
- [ ] Secrets reference full dotted paths
- [ ] Security contexts on all pods (both pod-level and container-level) - [ ] Security contexts on all pods (both pod-level and container-level)
- [ ] Simple component labels, no Helm-style labels - [ ] Simple component labels, no Helm-style labels
- [ ] Ingresses include external-dns annotations - [ ] Ingresses include external-dns annotations

182
CLAUDE.md
View File

@@ -1,171 +1,15 @@
# CLAUDE.md - @README.md
- @ADDING-APPS.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## IMPORTANT
## Overview - When adding a new app to the directory, check to make sure you are adding the latest app version.
- Use traefik for ingress.
This repository contains the Wild Cloud apps directory - a collection of Kubernetes applications packaged as Kustomize configurations. Each app is a self-contained directory with standardized manifests that can be deployed to Wild Cloud clusters using Wild Cloud CLI tools. - Use postgres for database if supported.
- Keep config key naming (including nesting) consistent with other apps.
## Repository Architecture - Don't use helm
- If the app requires a specific platform (amd64, arm64, etc.), make sure it is called out in the manifest and the k8s tags are set
### App Structure - when developing a new app:
- test with:
Each app follows a strict structure: - reset to a fresh state between tests:
- **`manifest.yaml`** - App metadata, dependencies, default configuration, and secret requirements - secrets.yaml is not checked in and any values unrelated to your current task should be preserved
- **`kustomization.yaml`** - Kustomize configuration with standard Wild Cloud labels
- **Resource files** - Kubernetes objects (deployments, services, ingresses, PVCs, jobs, etc.)
### Templating System
Configuration files use **gomplate templating** to reference operator configuration:
- Use `{{ .cloud.domain }}` for the operator's domain
- Use `{{ .apps.appname.configKey }}` for app-specific configuration
- Use `{{ .operator.email }}` for operator email
- All template variables must be defined in either the app's `manifest.yaml` under `defaultConfig` or be standard Wild Cloud operator variables
Templates are compiled when users add apps to their Wild Cloud instance via the web app, CLI, or API.
### Label Strategy
Wild Cloud uses a consistent labeling approach powered by Kustomize's `includeSelectors: true` feature:
```yaml
labels:
- includeSelectors: true
pairs:
app: appname # App name (matches directory)
managedBy: kustomize
partOf: wild-cloud
```
This automatically applies labels to all resources AND their selectors. Individual resources can use simple component-specific selectors like `component: web` or `component: worker`, and Kustomize will expand them to include the standard Wild Cloud labels.
**Important:** Do NOT use Helm-style labels (`app.kubernetes.io/name`, `app.kubernetes.io/instance`). Use simple component labels instead.
## Working with Apps
### Creating/Modifying Apps
1. **Manifest fields:**
- `name` - Must match directory name
- `description` - Brief app description
- `version` - App version (follow upstream versioning)
- `icon` - URL to app icon
- `requires` - List of dependency apps (e.g., `postgres`, `redis`, `memcached`)
- `defaultConfig` - Default configuration values (will be added to operator's `config.yaml`)
- `requiredSecrets` - List of secrets in dotted path format (e.g., `apps.appname.dbPassword`)
2. **Kustomization requirements:**
- Must include standard Wild Cloud labels with `includeSelectors: true`
- Namespace must match app name
- List all resource files under `resources:`
3. **Security contexts:**
All pods must comply with Pod Security Standards:
```yaml
securityContext:
runAsNonRoot: true
runAsUser: 999 # Use appropriate non-root UID
runAsGroup: 999
seccompProfile:
type: RuntimeDefault
containers:
- securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false # Set to true when possible
```
### Secrets Management
Secrets use **full dotted paths** as keys:
```yaml
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: appname-secrets
key: apps.appname.dbPassword # Full path, not just "dbPassword"
```
**Database URL secrets:** When apps need database URLs with embedded credentials, always use a dedicated `dbUrl` secret in `requiredSecrets`. Do NOT try to construct URLs with env var substitution in templates - Kustomize cannot process runtime environment variables.
### Database Initialization Jobs
Apps requiring PostgreSQL/MySQL databases should include a `db-init-job.yaml`:
- Creates the app database if it doesn't exist
- Creates/updates the app user with proper password
- Grants appropriate permissions
- For PostgreSQL jobs: use `runAsUser: 999` (postgres user)
- Include any required database extensions (e.g., Immich requires `vector`, `cube`, `earthdistance`)
Examples: [immich/db-init-job.yaml](immich/db-init-job.yaml), [gitea/db-init-job.yaml](gitea/db-init-job.yaml)
### External DNS Configuration
Ingress resources should include external-dns annotations:
```yaml
annotations:
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }}
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
```
This creates CNAME records pointing app subdomains to the main cluster domain.
### Converting Helm Charts
Wild Cloud prefers Kustomize over Helm for transparency and Git-friendliness. To convert a Helm chart:
```bash
helm fetch --untar --untardir charts repo/chart-name
helm template --output-dir base --namespace namespace --values values.yaml release-name charts/chart-name
cd base/chart-name
kustomize create --autodetect
```
Then:
1. Add `manifest.yaml`
2. Replace hardcoded values with gomplate variables
3. Update secrets to use Wild Cloud's dotted-path approach
4. Replace Helm labels with simple component labels
5. Add standard Wild Cloud labels to `kustomization.yaml`
6. Add security contexts to all pods
## Managing Wild Cloud Apps
Users can manage apps through:
1. **Web App**: Navigate to the Apps page in your instance to browse, add, configure, and deploy apps
2. **CLI**: Use the Wild CLI for terminal-based workflows:
- `wild app list` - List all available apps
- `wild app add <app-name>` - Add an app (compiles templates, updates config/secrets)
- `wild app deploy <app-name>` - Deploy an app to the cluster
- `wild app list-deployed` - List deployed apps
- `wild app status <app-name>` - Get app status
- `wild app delete <app-name>` - Delete an app
3. **API**: Use the Wild Central API endpoints for automation:
- `GET /api/v1/apps/available` - List all available apps
- `GET /api/v1/apps/available/{app-name}` - Get app details
- `POST /api/v1/instances/{instance-name}/apps` - Add an app (compiles templates, updates config/secrets)
- `POST /api/v1/instances/{instance-name}/apps/{app-name}/deploy` - Deploy an app to the cluster
- `GET /api/v1/instances/{instance-name}/apps` - List deployed apps
- `GET /api/v1/instances/{instance-name}/apps/{app-name}/status` - Get app status
## Validation
Before submitting changes:
1. Ensure `manifest.yaml` has all required fields
2. Verify `kustomization.yaml` includes standard Wild Cloud labels
3. Check all templates use valid configuration paths defined in `defaultConfig`
4. Confirm secrets use full dotted-path keys
5. Verify all pods have proper security contexts
6. Test template compilation works with sample operator config
## Examples of Well-Structured Apps
- **immich** - Multi-deployment app with database init, multiple services, PostgreSQL extensions
- **openproject** - App with multiple dependencies (postgres, memcached), configmap-based configuration
- **gitea** - Standard app with database init, PVC for storage

View File

@@ -1,31 +0,0 @@
---
# Source: discourse/templates/configmaps.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: discourse
namespace: discourse
data:
DISCOURSE_HOSTNAME: "{{ .apps.discourse.domain }}"
DISCOURSE_SKIP_INSTALL: "no"
DISCOURSE_SITE_NAME: "{{ .apps.discourse.siteName }}"
DISCOURSE_USERNAME: "{{ .apps.discourse.adminUsername }}"
DISCOURSE_EMAIL: "{{ .apps.discourse.adminEmail }}"
DISCOURSE_REDIS_HOST: "{{ .apps.discourse.redisHostname }}"
DISCOURSE_REDIS_PORT_NUMBER: "6379"
DISCOURSE_DATABASE_HOST: "{{ .apps.discourse.dbHostname }}"
DISCOURSE_DATABASE_PORT_NUMBER: "5432"
DISCOURSE_DATABASE_NAME: "{{ .apps.discourse.dbName }}"
DISCOURSE_DATABASE_USER: "{{ .apps.discourse.dbUsername }}"
DISCOURSE_SMTP_HOST: "{{ .apps.discourse.smtp.host }}"
DISCOURSE_SMTP_PORT: "{{ .apps.discourse.smtp.port }}"
DISCOURSE_SMTP_USER: "{{ .apps.discourse.smtp.user }}"
DISCOURSE_SMTP_PROTOCOL: "tls"
DISCOURSE_SMTP_AUTH: "login"
# DISCOURSE_PRECOMPILE_ASSETS: "false"
# DISCOURSE_SKIP_INSTALL: "no"
# DISCOURSE_SKIP_BOOTSTRAP: "yes"

View File

@@ -27,7 +27,7 @@ spec:
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
env: env:
- name: PGHOST - name: PGHOST
value: "{{ .apps.discourse.dbHostname }}" value: "{{ .dbHostname }}"
- name: PGPORT - name: PGPORT
value: "5432" value: "5432"
- name: PGUSER - name: PGUSER
@@ -36,16 +36,16 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: discourse-secrets name: discourse-secrets
key: apps.postgres.password key: postgres.password
- name: DISCOURSE_DB_USER - name: DISCOURSE_DB_USER
value: "{{ .apps.discourse.dbUsername }}" value: "{{ .dbUsername }}"
- name: DISCOURSE_DB_NAME - name: DISCOURSE_DB_NAME
value: "{{ .apps.discourse.dbName }}" value: "{{ .dbName }}"
- name: DISCOURSE_DB_PASSWORD - name: DISCOURSE_DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: discourse-secrets name: discourse-secrets
key: apps.discourse.dbPassword key: dbPassword
command: command:
- /bin/sh - /bin/sh
- -c - -c

View File

@@ -1,5 +1,4 @@
--- ---
# Source: discourse/templates/deployment.yaml
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
@@ -18,219 +17,133 @@ spec:
component: web component: web
spec: spec:
automountServiceAccountToken: false automountServiceAccountToken: false
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
component: web
topologyKey: kubernetes.io/hostname
weight: 1
serviceAccountName: discourse serviceAccountName: discourse
securityContext: securityContext:
fsGroup: 0 fsGroup: 0
fsGroupChangePolicy: Always fsGroupChangePolicy: Always
supplementalGroups: []
sysctls: []
initContainers:
containers: containers:
- name: discourse - name: discourse
image: docker.io/bitnami/discourse:3.4.7-debian-12-r0 image: tiredofit/discourse:latest
imagePullPolicy: "IfNotPresent" imagePullPolicy: "IfNotPresent"
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
capabilities: capabilities:
drop:
- ALL
add: add:
- CHOWN - CHOWN
- SYS_CHROOT - DAC_OVERRIDE
- FOWNER - FOWNER
- SETGID - SETGID
- SETUID - SETUID
- DAC_OVERRIDE
drop:
- ALL
privileged: false privileged: false
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
runAsGroup: 0
runAsNonRoot: false runAsNonRoot: false
runAsUser: 0 runAsUser: 0
seLinuxOptions: {}
seccompProfile: seccompProfile:
type: RuntimeDefault type: RuntimeDefault
env: env:
- name: BITNAMI_DEBUG # Admin configuration
value: "false" - name: ADMIN_USER
- name: DISCOURSE_PASSWORD value: {{ .adminUsername }}
- name: ADMIN_EMAIL
value: {{ .adminEmail }}
- name: ADMIN_PASS
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: discourse-secrets name: discourse-secrets
key: apps.discourse.adminPassword key: adminPassword
- name: DISCOURSE_PORT_NUMBER # Site configuration
value: "8080" - name: SITE_TITLE
- name: DISCOURSE_EXTERNAL_HTTP_PORT_NUMBER value: {{ .siteName }}
value: "80" - name: HOSTNAME
- name: DISCOURSE_DATABASE_PASSWORD value: {{ .domain }}
# Database configuration
- name: DB_HOST
value: {{ .dbHostname }}
- name: DB_PORT
value: "{{ .dbPort }}"
- name: DB_NAME
value: {{ .dbName }}
- name: DB_USER
value: {{ .dbUsername }}
- name: DB_PASS
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: discourse-secrets name: discourse-secrets
key: apps.discourse.dbPassword key: dbPassword
- name: POSTGRESQL_CLIENT_CREATE_DATABASE_PASSWORD # Redis configuration
- name: REDIS_HOST
value: {{ .redisHostname }}
- name: REDIS_PASS
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: discourse-secrets name: discourse-secrets
key: apps.discourse.dbPassword key: redis.password
- name: DISCOURSE_REDIS_PASSWORD # SMTP configuration
- name: SMTP_ENABLED
value: "{{ .smtp.enabled }}"
- name: SMTP_HOST
value: {{ .smtp.host }}
- name: SMTP_PORT
value: "{{ .smtp.port }}"
- name: SMTP_USER
value: {{ .smtp.user }}
- name: SMTP_PASS
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: discourse-secrets name: discourse-secrets
key: apps.redis.password key: smtpPassword
- name: DISCOURSE_SECRET_KEY_BASE - name: SMTP_TLS
valueFrom: value: "{{ .smtp.tls }}"
secretKeyRef: # Container timezone
name: discourse-secrets - name: TZ
key: apps.discourse.secretKeyBase value: {{ .timezone }}
- name: DISCOURSE_SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: apps.discourse.smtpPassword
- name: SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: apps.discourse.smtpPassword
envFrom:
- configMapRef:
name: discourse
ports: ports:
- name: http - name: http
containerPort: 8080 containerPort: 3000
protocol: TCP protocol: TCP
livenessProbe: livenessProbe:
tcpSocket: httpGet:
path: /
port: http port: http
initialDelaySeconds: 500 initialDelaySeconds: 420
periodSeconds: 10 periodSeconds: 30
timeoutSeconds: 5 timeoutSeconds: 10
successThreshold: 1 successThreshold: 1
failureThreshold: 6 failureThreshold: 6
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /srv/status path: /
port: http port: http
initialDelaySeconds: 180 initialDelaySeconds: 360
periodSeconds: 10 periodSeconds: 30
timeoutSeconds: 5 timeoutSeconds: 10
successThreshold: 1 successThreshold: 1
failureThreshold: 6 failureThreshold: 6
resources: resources:
limits: limits:
cpu: 1 cpu: 2000m
ephemeral-storage: 2Gi ephemeral-storage: 10Gi
memory: 8Gi # for precompiling assets! memory: 4Gi
requests: requests:
cpu: 750m cpu: 500m
ephemeral-storage: 50Mi ephemeral-storage: 50Mi
memory: 1Gi memory: 1Gi
volumeMounts: volumeMounts:
- name: discourse-data - name: discourse-logs
mountPath: /bitnami/discourse mountPath: /data/logs
subPath: discourse - name: discourse-uploads
- name: sidekiq mountPath: /data/uploads
image: docker.io/bitnami/discourse:3.4.7-debian-12-r0 - name: discourse-backups
imagePullPolicy: "IfNotPresent" mountPath: /data/backups
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- CHOWN
- SYS_CHROOT
- FOWNER
- SETGID
- SETUID
- DAC_OVERRIDE
drop:
- ALL
privileged: false
readOnlyRootFilesystem: false
runAsGroup: 0
runAsNonRoot: false
runAsUser: 0
seLinuxOptions: {}
seccompProfile:
type: RuntimeDefault
command:
- /opt/bitnami/scripts/discourse/entrypoint.sh
args:
- /opt/bitnami/scripts/discourse-sidekiq/run.sh
env:
- name: BITNAMI_DEBUG
value: "false"
- name: DISCOURSE_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: apps.discourse.adminPassword
- name: DISCOURSE_POSTGRESQL_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: apps.discourse.dbPassword
- name: DISCOURSE_REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: apps.redis.password
- name: DISCOURSE_SECRET_KEY_BASE
valueFrom:
secretKeyRef:
name: discourse-secrets
key: apps.discourse.secretKeyBase
- name: DISCOURSE_SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: apps.discourse.smtpPassword
- name: SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: apps.discourse.smtpPassword
envFrom:
- configMapRef:
name: discourse
livenessProbe:
exec:
command: ["/bin/sh", "-c", "pgrep -f ^sidekiq"]
initialDelaySeconds: 500
periodSeconds: 10
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 6
readinessProbe:
exec:
command: ["/bin/sh", "-c", "pgrep -f ^sidekiq"]
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 6
resources:
limits:
cpu: 500m
ephemeral-storage: 2Gi
memory: 768Mi
requests:
cpu: 375m
ephemeral-storage: 50Mi
memory: 512Mi
volumeMounts:
- name: discourse-data
mountPath: /bitnami/discourse
subPath: discourse
volumes: volumes:
- name: discourse-data - name: discourse-logs
persistentVolumeClaim: persistentVolumeClaim:
claimName: discourse claimName: discourse-logs
- name: discourse-uploads
persistentVolumeClaim:
claimName: discourse-uploads
- name: discourse-backups
persistentVolumeClaim:
claimName: discourse-backups

View File

@@ -4,13 +4,13 @@ apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: discourse name: discourse
namespace: "discourse" namespace: "{{ .namespace }}"
annotations: annotations:
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false" external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
external-dns.alpha.kubernetes.io/target: "{{ .cloud.domain }}" external-dns.alpha.kubernetes.io/target: "{{ .externalDnsDomain }}"
spec: spec:
rules: rules:
- host: "{{ .apps.discourse.domain }}" - host: "{{ .domain }}"
http: http:
paths: paths:
- path: / - path: /
@@ -22,5 +22,5 @@ spec:
name: http name: http
tls: tls:
- hosts: - hosts:
- "{{ .apps.discourse.domain }}" - "{{ .domain }}"
secretName: wildcard-external-wild-cloud-tls secretName: wildcard-external-wild-cloud-tls

View File

@@ -10,7 +10,6 @@ labels:
resources: resources:
- namespace.yaml - namespace.yaml
- serviceaccount.yaml - serviceaccount.yaml
- configmap.yaml
- pvc.yaml - pvc.yaml
- deployment.yaml - deployment.yaml
- service.yaml - service.yaml

View File

@@ -1,22 +1,25 @@
name: discourse name: discourse
description: Discourse is a modern, open-source discussion platform designed for online communities and forums. description: Discourse is a modern, open-source discussion platform designed for online communities and forums.
version: 3.4.7 version: 3.5.3
icon: https://www.discourse.org/img/icon.png icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/discourse.svg
requires: requires:
- name: postgres - name: postgres
- name: redis - name: redis
defaultConfig: defaultConfig:
namespace: discourse
externalDnsDomain: "{{ .cloud.domain }}"
timezone: UTC timezone: UTC
port: 8080 port: 3000
storage: 10Gi storage: 10Gi
adminEmail: admin@{{ .cloud.domain }} adminEmail: "{{ .operator.email }}"
adminUsername: admin adminUsername: admin
siteName: "Community" siteName: "Community"
domain: discourse.{{ .cloud.domain }} domain: discourse.{{ .cloud.domain }}
dbHostname: postgres.postgres.svc.cluster.local dbHostname: "{{ .apps.postgres.host }}"
dbPort: "{{ .apps.postgres.port }}"
dbUsername: discourse dbUsername: discourse
dbName: discourse dbName: discourse
redisHostname: redis.redis.svc.cluster.local redisHostname: "{{ .apps.redis.host }}"
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
smtp: smtp:
enabled: false enabled: false
@@ -24,13 +27,16 @@ defaultConfig:
port: "{{ .cloud.smtp.port }}" port: "{{ .cloud.smtp.port }}"
user: "{{ .cloud.smtp.user }}" user: "{{ .cloud.smtp.user }}"
from: "{{ .cloud.smtp.from }}" from: "{{ .cloud.smtp.from }}"
tls: {{ .cloud.smtp.tls }} tls: "{{ .cloud.smtp.tls }}"
startTls: {{ .cloud.smtp.startTls }} startTls: "{{ .cloud.smtp.startTls }}"
defaultSecrets:
- key: adminPassword
- key: secretKeyBase
default: "{{ random.AlphaNum 64 }}"
- key: smtpPassword
- key: dbPassword
- key: dbUrl
default: "postgres://{{ .app.dbUsername }}:{{ .secrets.dbPassword }}@{{ .app.dbHostname }}:{{ .app.dbPort }}/{{ .app.dbName }}?sslmode=disable"
requiredSecrets: requiredSecrets:
- apps.discourse.adminPassword - postgres.password
- apps.discourse.dbPassword - redis.password
- apps.discourse.dbUrl
- apps.redis.password
- apps.discourse.secretKeyBase
- apps.discourse.smtpPassword
- apps.postgres.password

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: discourse name: "{{ .namespace }}"

View File

@@ -1,13 +1,39 @@
--- ---
# Source: discourse/templates/pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim
metadata: metadata:
name: discourse name: discourse-logs
namespace: discourse namespace: discourse
spec: spec:
accessModes: accessModes:
- "ReadWriteOnce" - ReadWriteOnce
resources: resources:
requests: requests:
storage: "{{ .apps.discourse.storage }}" storage: 2Gi
storageClassName: longhorn
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: discourse-uploads
namespace: discourse
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .storage }}
storageClassName: longhorn
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: discourse-backups
namespace: discourse
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: longhorn

View File

@@ -3,7 +3,6 @@ apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: example-admin name: example-admin
namespace: example-admin
labels: labels:
app: example-admin app: example-admin
spec: spec:

View File

@@ -3,10 +3,9 @@ apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: example-admin name: example-admin
namespace: example-admin
spec: spec:
rules: rules:
- host: example-admin.{{ .cloud.internalDomain }} - host: "{{ .host }}"
http: http:
paths: paths:
- path: / - path: /
@@ -18,5 +17,5 @@ spec:
number: 80 number: 80
tls: tls:
- hosts: - hosts:
- example-admin.{{ .cloud.internalDomain }} - "{{ .host }}"
secretName: wildcard-internal-wild-cloud-tls secretName: wildcard-internal-wild-cloud-tls

View File

@@ -3,4 +3,7 @@ install: true
description: An example application that is deployed with internal-only access. description: An example application that is deployed with internal-only access.
version: 1.0.0 version: 1.0.0
defaultConfig: defaultConfig:
namespace: example-admin
externalDnsDomain: '{{ .cloud.domain }}'
host: '{{ .host }}'
tlsSecretName: wildcard-internal-wild-cloud-tls tlsSecretName: wildcard-internal-wild-cloud-tls

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: example-admin name: "{{ .namespace }}"

View File

@@ -4,7 +4,7 @@ kind: Ingress
metadata: metadata:
name: example-app name: example-app
annotations: annotations:
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }} external-dns.alpha.kubernetes.io/target: "{{ .cloud.externalDnsTarget }}"
external-dns.alpha.kubernetes.io/cloudflare-proxied: false external-dns.alpha.kubernetes.io/cloudflare-proxied: false
# Optional: Enable HTTPS redirection # Optional: Enable HTTPS redirection
@@ -15,7 +15,7 @@ metadata:
# traefik.ingress.kubernetes.io/auth-secret: basic-auth # traefik.ingress.kubernetes.io/auth-secret: basic-auth
spec: spec:
rules: rules:
- host: example-app.{{ .cloud.domain }} - host: "{{ .host }}"
http: http:
paths: paths:
- path: / - path: /
@@ -27,5 +27,5 @@ spec:
number: 80 number: 80
tls: tls:
- hosts: - hosts:
- example-app.{{ .cloud.domain }} - "{{ .host }}"
secretName: wildcard-wild-cloud-tls secretName: wildcard-wild-cloud-tls

View File

@@ -3,4 +3,7 @@ install: true
description: An example application that is deployed with public access. description: An example application that is deployed with public access.
version: 1.0.0 version: 1.0.0
defaultConfig: defaultConfig:
namespace: example-app
externalDnsDomain: '{{ .cloud.domain }}'
host: example-app.{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: example-app name: "{{ .namespace }}"

View File

@@ -12,7 +12,7 @@ spec:
spec: spec:
containers: containers:
- name: db-init - name: db-init
image: {{ .apps.mysql.image }} image: mysql:9.1.0
command: ["/bin/bash", "-c"] command: ["/bin/bash", "-c"]
args: args:
- | - |
@@ -27,18 +27,18 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: mysql-secrets name: mysql-secrets
key: apps.mysql.rootPassword key: rootPassword
- name: DB_HOSTNAME - name: DB_HOSTNAME
value: "{{ .apps.ghost.dbHost }}" value: "{{ .dbHost }}"
- name: DB_PORT - name: DB_PORT
value: "{{ .apps.ghost.dbPort }}" value: "{{ .dbPort }}"
- name: DB_DATABASE_NAME - name: DB_DATABASE_NAME
value: "{{ .apps.ghost.dbName }}" value: "{{ .dbName }}"
- name: DB_USERNAME - name: DB_USERNAME
value: "{{ .apps.ghost.dbUser }}" value: "{{ .dbUser }}"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: ghost-secrets name: ghost-secrets
key: apps.ghost.dbPassword key: dbPassword
restartPolicy: OnFailure restartPolicy: OnFailure

View File

@@ -17,10 +17,10 @@ spec:
spec: spec:
containers: containers:
- name: ghost - name: ghost
image: {{ .apps.ghost.image }} image: {{ .image }}
ports: ports:
- name: http - name: http
containerPort: {{ .apps.ghost.port }} containerPort: {{ .port }}
protocol: TCP protocol: TCP
env: env:
- name: BITNAMI_DEBUG - name: BITNAMI_DEBUG
@@ -28,33 +28,33 @@ spec:
- name: ALLOW_EMPTY_PASSWORD - name: ALLOW_EMPTY_PASSWORD
value: "yes" value: "yes"
- name: GHOST_DATABASE_HOST - name: GHOST_DATABASE_HOST
value: {{ .apps.ghost.dbHost }} value: {{ .dbHost }}
- name: GHOST_DATABASE_PORT_NUMBER - name: GHOST_DATABASE_PORT_NUMBER
value: "{{ .apps.ghost.dbPort }}" value: "{{ .dbPort }}"
- name: GHOST_DATABASE_NAME - name: GHOST_DATABASE_NAME
value: {{ .apps.ghost.dbName }} value: {{ .dbName }}
- name: GHOST_DATABASE_USER - name: GHOST_DATABASE_USER
value: {{ .apps.ghost.dbUser }} value: {{ .dbUser }}
- name: GHOST_DATABASE_PASSWORD - name: GHOST_DATABASE_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: ghost-secrets name: ghost-secrets
key: apps.ghost.dbPassword key: dbPassword
- name: GHOST_HOST - name: GHOST_HOST
value: {{ .apps.ghost.domain }} value: {{ .domain }}
- name: GHOST_PORT_NUMBER - name: GHOST_PORT_NUMBER
value: "{{ .apps.ghost.port }}" value: "{{ .port }}"
- name: GHOST_USERNAME - name: GHOST_USERNAME
value: {{ .apps.ghost.adminUser }} value: {{ .adminUser }}
- name: GHOST_PASSWORD - name: GHOST_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: ghost-secrets name: ghost-secrets
key: apps.ghost.adminPassword key: adminPassword
- name: GHOST_EMAIL - name: GHOST_EMAIL
value: {{ .apps.ghost.adminEmail }} value: {{ .adminEmail }}
- name: GHOST_BLOG_TITLE - name: GHOST_BLOG_TITLE
value: {{ .apps.ghost.blogTitle }} value: {{ .blogTitle }}
- name: GHOST_ENABLE_HTTPS - name: GHOST_ENABLE_HTTPS
value: "yes" value: "yes"
- name: GHOST_EXTERNAL_HTTP_PORT_NUMBER - name: GHOST_EXTERNAL_HTTP_PORT_NUMBER
@@ -66,18 +66,18 @@ spec:
- name: GHOST_SMTP_SERVICE - name: GHOST_SMTP_SERVICE
value: SMTP value: SMTP
- name: GHOST_SMTP_HOST - name: GHOST_SMTP_HOST
value: {{ .apps.ghost.smtp.host }} value: {{ .smtp.host }}
- name: GHOST_SMTP_PORT - name: GHOST_SMTP_PORT
value: "{{ .apps.ghost.smtp.port }}" value: "{{ .smtp.port }}"
- name: GHOST_SMTP_USER - name: GHOST_SMTP_USER
value: {{ .apps.ghost.smtp.user }} value: {{ .smtp.user }}
- name: GHOST_SMTP_PASSWORD - name: GHOST_SMTP_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: ghost-secrets name: ghost-secrets
key: apps.ghost.smtpPassword key: smtpPassword
- name: GHOST_SMTP_FROM_ADDRESS - name: GHOST_SMTP_FROM_ADDRESS
value: {{ .apps.ghost.smtp.from }} value: {{ .smtp.from }}
resources: resources:
limits: limits:
cpu: 375m cpu: 375m
@@ -92,7 +92,7 @@ spec:
mountPath: /bitnami/ghost mountPath: /bitnami/ghost
livenessProbe: livenessProbe:
tcpSocket: tcpSocket:
port: {{ .apps.ghost.port }} port: {{ .port }}
initialDelaySeconds: 120 initialDelaySeconds: 120
timeoutSeconds: 5 timeoutSeconds: 5
periodSeconds: 10 periodSeconds: 10

View File

@@ -7,12 +7,12 @@ metadata:
kubernetes.io/ingress.class: "traefik" kubernetes.io/ingress.class: "traefik"
cert-manager.io/cluster-issuer: "letsencrypt-prod" cert-manager.io/cluster-issuer: "letsencrypt-prod"
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false" external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }} external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
external-dns.alpha.kubernetes.io/ttl: "60" external-dns.alpha.kubernetes.io/ttl: "60"
traefik.ingress.kubernetes.io/redirect-entry-point: https traefik.ingress.kubernetes.io/redirect-entry-point: https
spec: spec:
rules: rules:
- host: {{ .apps.ghost.domain }} - host: {{ .domain }}
http: http:
paths: paths:
- path: / - path: /
@@ -24,5 +24,5 @@ spec:
number: 80 number: 80
tls: tls:
- hosts: - hosts:
- {{ .apps.ghost.domain }} - {{ .domain }}
secretName: {{ .apps.ghost.tlsSecretName }} secretName: {{ .tlsSecretName }}

View File

@@ -1,10 +1,13 @@
name: ghost name: ghost
description: Ghost is a powerful app for new-media creators to publish, share, and grow a business around their content. description: Ghost is a powerful app for new-media creators to publish, share, and
grow a business around their content.
version: 5.118.1 version: 5.118.1
icon: https://ghost.org/images/logos/ghost-logo-orb.png icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/ghost.png
requires: requires:
- name: mysql - name: mysql
defaultConfig: defaultConfig:
namespace: ghost
externalDnsDomain: '{{ .cloud.domain }}'
image: docker.io/bitnami/ghost:5.118.1-debian-12-r0 image: docker.io/bitnami/ghost:5.118.1-debian-12-r0
domain: ghost.{{ .cloud.domain }} domain: ghost.{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
@@ -15,16 +18,17 @@ defaultConfig:
dbName: ghost dbName: ghost
dbUser: ghost dbUser: ghost
adminUser: admin adminUser: admin
adminEmail: "admin@{{ .cloud.domain }}" adminEmail: {{ .operator.email }}
blogTitle: "My Blog" blogTitle: My Blog
timezone: UTC timezone: UTC
tlsSecretName: wildcard-wild-cloud-tls
smtp: smtp:
host: "{{ .cloud.smtp.host }}" host: '{{ .cloud.smtp.host }}'
port: "{{ .cloud.smtp.port }}" port: '{{ .cloud.smtp.port }}'
from: "{{ .cloud.smtp.from }}" from: '{{ .cloud.smtp.from }}'
user: "{{ .cloud.smtp.user }}" user: '{{ .cloud.smtp.user }}'
defaultSecrets:
- key: adminPassword
- key: dbPassword
- key: smtpPassword
requiredSecrets: requiredSecrets:
- apps.ghost.adminPassword - mysql.rootPassword
- apps.ghost.dbPassword
- apps.ghost.smtpPassword

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: ghost name: "{{ .namespace }}"

View File

@@ -8,4 +8,4 @@ spec:
- ReadWriteOnce - ReadWriteOnce
resources: resources:
requests: requests:
storage: {{ .apps.ghost.storage }} storage: {{ .storage }}

View File

@@ -9,6 +9,6 @@ spec:
- name: http - name: http
port: 80 port: 80
protocol: TCP protocol: TCP
targetPort: {{ .apps.ghost.port }} targetPort: {{ .port }}
selector: selector:
component: web component: web

View File

@@ -20,7 +20,7 @@ Sensitive configuration is stored in the `gitea-secrets` secret and managed by t
- `dbPassword` - Database password - `dbPassword` - Database password
- `smtpPassword` - SMTP authentication password - `smtpPassword` - SMTP authentication password
Secrets are defined in `secrets.yaml` and listed in `manifest.yaml` under `requiredSecrets`. When deploying, the system automatically ensures all required secrets exist in the `gitea-secrets` secret before deployment. Secrets are defined in `secrets.yaml` and listed in `manifest.yaml` under `defaultSecrets`. When deploying, the system automatically ensures all required secrets exist in the `gitea-secrets` secret before deployment.
### Persistent Configuration (app.ini) ### Persistent Configuration (app.ini)
Gitea manages its own `app.ini` file on persistent storage for: Gitea manages its own `app.ini` file on persistent storage for:
@@ -46,7 +46,7 @@ Gitea manages its own `app.ini` file on persistent storage for:
### Secret Settings ### Secret Settings
1. Edit `secrets.yaml` with your secret values 1. Edit `secrets.yaml` with your secret values
2. Ensure the secret key is listed in `manifest.yaml` under `requiredSecrets` 2. Ensure the secret key is listed in `manifest.yaml` under `defaultSecrets`
3. Deploy the app via the web app, CLI, or API - this will automatically update the `gitea-secrets` secret and restart the pod 3. Deploy the app via the web app, CLI, or API - this will automatically update the `gitea-secrets` secret and restart the pod
### Web UI Changes ### Web UI Changes

View File

@@ -12,7 +12,7 @@ spec:
spec: spec:
containers: containers:
- name: db-init - name: db-init
image: {{ .apps.postgres.image }} image: postgres:17
command: ["/bin/bash", "-c"] command: ["/bin/bash", "-c"]
args: args:
- | - |
@@ -36,16 +36,16 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: postgres-secrets name: postgres-secrets
key: apps.postgres.password key: password
- name: DB_HOSTNAME - name: DB_HOSTNAME
value: "{{ .apps.gitea.dbHost }}" value: "{{ .dbHost }}"
- name: DB_DATABASE_NAME - name: DB_DATABASE_NAME
value: "{{ .apps.gitea.dbName }}" value: "{{ .dbName }}"
- name: DB_USERNAME - name: DB_USERNAME
value: "{{ .apps.gitea.dbUser }}" value: "{{ .dbUser }}"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: gitea-secrets name: gitea-secrets
key: apps.gitea.dbPassword key: dbPassword
restartPolicy: OnFailure restartPolicy: OnFailure

View File

@@ -23,7 +23,7 @@ spec:
terminationGracePeriodSeconds: 60 terminationGracePeriodSeconds: 60
containers: containers:
- name: gitea - name: gitea
image: "{{ .apps.gitea.image }}" image: "{{ .image }}"
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
envFrom: envFrom:
- configMapRef: - configMapRef:
@@ -33,27 +33,27 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: gitea-secrets name: gitea-secrets
key: apps.gitea.adminPassword key: adminPassword
- name: GITEA__security__SECRET_KEY - name: GITEA__security__SECRET_KEY
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: gitea-secrets name: gitea-secrets
key: apps.gitea.secretKey key: secretKey
- name: GITEA__security__INTERNAL_TOKEN - name: GITEA__security__INTERNAL_TOKEN
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: gitea-secrets name: gitea-secrets
key: apps.gitea.jwtSecret key: jwtSecret
- name: GITEA__database__PASSWD - name: GITEA__database__PASSWD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: gitea-secrets name: gitea-secrets
key: apps.gitea.dbPassword key: dbPassword
- name: GITEA__mailer__PASSWD - name: GITEA__mailer__PASSWD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: gitea-secrets name: gitea-secrets
key: apps.gitea.smtpPassword key: smtpPassword
ports: ports:
- name: ssh - name: ssh
containerPort: 2222 containerPort: 2222

View File

@@ -3,12 +3,12 @@ SSH_PORT=22
GITEA_WORK_DIR=/data GITEA_WORK_DIR=/data
GITEA_TEMP=/tmp/gitea GITEA_TEMP=/tmp/gitea
TMPDIR=/tmp/gitea TMPDIR=/tmp/gitea
GITEA_ADMIN_USERNAME={{ .apps.gitea.adminUser }} GITEA_ADMIN_USERNAME={{ .adminUser }}
GITEA_ADMIN_PASSWORD_MODE=keepUpdated GITEA_ADMIN_PASSWORD_MODE=keepUpdated
# Core app settings # Core app settings
GITEA____APP_NAME={{ .apps.gitea.appName }} GITEA____APP_NAME={{ .appName }}
GITEA____RUN_MODE={{ .apps.gitea.runMode }} GITEA____RUN_MODE={{ .runMode }}
GITEA____RUN_USER=git GITEA____RUN_USER=git
# Security settings # Security settings
@@ -17,19 +17,19 @@ GITEA__security__PASSWORD_HASH_ALGO=pbkdf2
# Database settings (except password which comes from secret) # Database settings (except password which comes from secret)
GITEA__database__DB_TYPE=postgres GITEA__database__DB_TYPE=postgres
GITEA__database__HOST={{ .apps.gitea.dbHost }}:{{ .apps.gitea.dbPort }} GITEA__database__HOST={{ .dbHost }}:{{ .dbPort }}
GITEA__database__NAME={{ .apps.gitea.dbName }} GITEA__database__NAME={{ .dbName }}
GITEA__database__USER={{ .apps.gitea.dbUser }} GITEA__database__USER={{ .dbUser }}
GITEA__database__SSL_MODE=disable GITEA__database__SSL_MODE=disable
GITEA__database__LOG_SQL=false GITEA__database__LOG_SQL=false
# Server settings # Server settings
GITEA__server__DOMAIN={{ .apps.gitea.domain }} GITEA__server__DOMAIN={{ .domain }}
GITEA__server__HTTP_PORT={{ .apps.gitea.port }} GITEA__server__HTTP_PORT={{ .port }}
GITEA__server__ROOT_URL=https://{{ .apps.gitea.domain }}/ GITEA__server__ROOT_URL=https://{{ .domain }}/
GITEA__server__DISABLE_SSH=false GITEA__server__DISABLE_SSH=false
GITEA__server__SSH_DOMAIN={{ .apps.gitea.domain }} GITEA__server__SSH_DOMAIN={{ .domain }}
GITEA__server__SSH_PORT={{ .apps.gitea.sshPort }} GITEA__server__SSH_PORT={{ .sshPort }}
GITEA__server__SSH_LISTEN_PORT=2222 GITEA__server__SSH_LISTEN_PORT=2222
GITEA__server__LFS_START_SERVER=true GITEA__server__LFS_START_SERVER=true
GITEA__server__OFFLINE_MODE=true GITEA__server__OFFLINE_MODE=true
@@ -53,8 +53,8 @@ GITEA__webhook__ALLOWED_HOST_LIST=*
# Mailer settings (enabled via env vars, password from secret) # Mailer settings (enabled via env vars, password from secret)
GITEA__mailer__ENABLED=true GITEA__mailer__ENABLED=true
GITEA__mailer__SMTP_ADDR={{ .apps.gitea.smtp.host }} GITEA__mailer__SMTP_ADDR={{ .smtp.host }}
GITEA__mailer__SMTP_PORT={{ .apps.gitea.smtp.port }} GITEA__mailer__SMTP_PORT={{ .smtp.port }}
GITEA__mailer__FROM={{ .apps.gitea.smtp.from }} GITEA__mailer__FROM={{ .smtp.from }}
GITEA__mailer__USER={{ .apps.gitea.smtp.user }} GITEA__mailer__USER={{ .smtp.user }}

View File

@@ -5,10 +5,10 @@ metadata:
namespace: gitea namespace: gitea
annotations: annotations:
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false" external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
external-dns.alpha.kubernetes.io/target: "{{ .cloud.domain }}" external-dns.alpha.kubernetes.io/target: "{{ .externalDnsDomain }}"
spec: spec:
rules: rules:
- host: "{{ .apps.gitea.domain }}" - host: "{{ .domain }}"
http: http:
paths: paths:
- path: / - path: /
@@ -19,6 +19,6 @@ spec:
port: port:
number: 3000 number: 3000
tls: tls:
- secretName: "{{ .apps.gitea.tlsSecretName }}" - secretName: "{{ .tlsSecretName }}"
hosts: hosts:
- "{{ .apps.gitea.domain }}" - "{{ .domain }}"

View File

@@ -1,10 +1,12 @@
name: gitea name: gitea
description: Gitea is a painless self-hosted Git service written in Go description: Gitea is a painless self-hosted Git service written in Go
version: 1.24.3 version: 1.24.3
icon: https://github.com/go-gitea/gitea/raw/main/assets/logo.png icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
requires: requires:
- name: postgres - name: postgres
defaultConfig: defaultConfig:
namespace: gitea
externalDnsDomain: '{{ .cloud.domain }}'
image: gitea/gitea:1.24.3 image: gitea/gitea:1.24.3
appName: Gitea appName: Gitea
domain: gitea.{{ .cloud.domain }} domain: gitea.{{ .cloud.domain }}
@@ -16,18 +18,20 @@ defaultConfig:
dbUser: gitea dbUser: gitea
dbHost: postgres.postgres.svc.cluster.local dbHost: postgres.postgres.svc.cluster.local
adminUser: admin adminUser: admin
adminEmail: "admin@{{ .cloud.domain }}" adminEmail: "{{ .operator.email }}"
dbPort: 5432 dbPort: 5432
timezone: UTC timezone: UTC
runMode: prod runMode: prod
smtp: smtp:
host: TBD host: '{{ .cloud.smtp.host }}'
port: 465 port: '{{ .cloud.smtp.port }}'
from: no-reply@{{ .cloud.domain }} user: '{{ .cloud.smtp.user }}'
user: TBD from: '{{ .cloud.smtp.from }}'
defaultSecrets:
- key: adminPassword
- key: dbPassword
- key: secretKey
- key: jwtSecret
- key: smtpPassword
requiredSecrets: requiredSecrets:
- apps.gitea.adminPassword - postgres.password
- apps.gitea.dbPassword
- apps.gitea.secretKey
- apps.gitea.jwtSecret
- apps.gitea.smtpPassword

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: gitea name: "{{ .namespace }}"

View File

@@ -9,4 +9,4 @@ spec:
storageClassName: longhorn storageClassName: longhorn
resources: resources:
requests: requests:
storage: "{{ .apps.gitea.storage }}" storage: "{{ .storage }}"

View File

@@ -8,7 +8,7 @@ spec:
ports: ports:
- name: http - name: http
port: 3000 port: 3000
targetPort: {{ .apps.gitea.port }} targetPort: {{ .port }}
selector: selector:
component: web component: web
--- ---
@@ -21,7 +21,7 @@ spec:
type: LoadBalancer type: LoadBalancer
ports: ports:
- name: ssh - name: ssh
port: {{ .apps.gitea.sshPort }} port: {{ .sshPort }}
targetPort: 2222 targetPort: 2222
protocol: TCP protocol: TCP
selector: selector:

View File

@@ -7,7 +7,7 @@ spec:
spec: spec:
containers: containers:
- name: db-init - name: db-init
image: {{ .apps.postgres.image }} image: postgres:17
command: ["/bin/bash", "-c"] command: ["/bin/bash", "-c"]
args: args:
- | - |
@@ -53,16 +53,16 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: immich-secrets name: immich-secrets
key: apps.postgres.password key: postgres.password
- name: DB_HOSTNAME - name: DB_HOSTNAME
value: "{{ .apps.immich.dbHostname }}" value: "{{ .dbHostname }}"
- name: DB_DATABASE_NAME - name: DB_DATABASE_NAME
value: "{{ .apps.immich.dbUsername }}" value: "{{ .dbUsername }}"
- name: DB_USERNAME - name: DB_USERNAME
value: "{{ .apps.immich.dbUsername }}" value: "{{ .dbUsername }}"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: immich-secrets name: immich-secrets
key: apps.immich.dbPassword key: dbPassword
restartPolicy: OnFailure restartPolicy: OnFailure

View File

@@ -15,14 +15,14 @@ spec:
component: machine-learning component: machine-learning
spec: spec:
containers: containers:
- image: "{{ .apps.immich.mlImage }}" - image: "{{ .mlImage }}"
name: immich-machine-learning name: immich-machine-learning
ports: ports:
- containerPort: {{ .apps.immich.mlPort }} - containerPort: {{ .mlPort }}
protocol: TCP protocol: TCP
env: env:
- name: TZ - name: TZ
value: "{{ .apps.immich.timezone }}" value: "{{ .timezone }}"
volumeMounts: volumeMounts:
- mountPath: /cache - mountPath: /cache
name: immich-cache name: immich-cache

View File

@@ -20,27 +20,27 @@ spec:
component: microservices component: microservices
spec: spec:
containers: containers:
- image: "{{ .apps.immich.serverImage }}" - image: "{{ .serverImage }}"
name: immich-microservices name: immich-microservices
env: env:
- name: REDIS_HOSTNAME - name: REDIS_HOSTNAME
value: "{{ .apps.immich.redisHostname }}" value: "{{ .redisHostname }}"
- name: REDIS_PASSWORD - name: REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: immich-secrets name: immich-secrets
key: apps.redis.password key: redis.password
- name: DB_HOSTNAME - name: DB_HOSTNAME
value: "{{ .apps.immich.dbHostname }}" value: "{{ .dbHostname }}"
- name: DB_USERNAME - name: DB_USERNAME
value: "{{ .apps.immich.dbUsername }}" value: "{{ .dbUsername }}"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: immich-secrets name: immich-secrets
key: apps.immich.dbPassword key: dbPassword
- name: TZ - name: TZ
value: "{{ .apps.immich.timezone }}" value: "{{ .timezone }}"
- name: IMMICH_WORKERS_EXCLUDE - name: IMMICH_WORKERS_EXCLUDE
value: api value: api
volumeMounts: volumeMounts:

View File

@@ -20,30 +20,30 @@ spec:
component: server component: server
spec: spec:
containers: containers:
- image: "{{ .apps.immich.serverImage }}" - image: "{{ .serverImage }}"
name: immich-server name: immich-server
ports: ports:
- containerPort: {{ .apps.immich.serverPort }} - containerPort: {{ .serverPort }}
protocol: TCP protocol: TCP
env: env:
- name: REDIS_HOSTNAME - name: REDIS_HOSTNAME
value: "{{ .apps.immich.redisHostname }}" value: "{{ .redisHostname }}"
- name: REDIS_PASSWORD - name: REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: immich-secrets name: immich-secrets
key: apps.redis.password key: redis.password
- name: DB_HOSTNAME - name: DB_HOSTNAME
value: "{{ .apps.immich.dbHostname }}" value: "{{ .dbHostname }}"
- name: DB_USERNAME - name: DB_USERNAME
value: "{{ .apps.immich.dbUsername }}" value: "{{ .dbUsername }}"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: immich-secrets name: immich-secrets
key: apps.immich.dbPassword key: dbPassword
- name: TZ - name: TZ
value: "{{ .apps.immich.timezone }}" value: "{{ .timezone }}"
- name: IMMICH_WORKERS_EXCLUDE - name: IMMICH_WORKERS_EXCLUDE
value: microservices value: microservices
volumeMounts: volumeMounts:

View File

@@ -4,11 +4,11 @@ kind: Ingress
metadata: metadata:
name: immich-public name: immich-public
annotations: annotations:
external-dns.alpha.kubernetes.io/target: "{{ .cloud.domain }}" external-dns.alpha.kubernetes.io/target: "{{ .externalDnsDomain }}"
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false" external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
spec: spec:
rules: rules:
- host: "{{ .apps.immich.domain }}" - host: "{{ .domain }}"
http: http:
paths: paths:
- path: / - path: /
@@ -21,4 +21,4 @@ spec:
tls: tls:
- secretName: wildcard-wild-cloud-tls - secretName: wildcard-wild-cloud-tls
hosts: hosts:
- "{{ .apps.immich.domain }}" - "{{ .domain }}"

View File

@@ -1,12 +1,15 @@
name: immich name: immich
install: true install: true
description: Immich is a self-hosted photo and video backup solution that allows you to store, manage, and share your media files securely. description: Immich is a self-hosted photo and video backup solution that allows you
version: 1.0.0 to store, manage, and share your media files securely.
icon: https://immich.app/assets/images/logo.png version: release
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
requires: requires:
- name: redis - name: redis
- name: postgres - name: postgres
defaultConfig: defaultConfig:
namespace: immich
externalDnsDomain: '{{ .cloud.domain }}'
serverImage: ghcr.io/immich-app/immich-server:release serverImage: ghcr.io/immich-app/immich-server:release
mlImage: ghcr.io/immich-app/immich-machine-learning:release mlImage: ghcr.io/immich-app/immich-machine-learning:release
timezone: UTC timezone: UTC
@@ -19,7 +22,8 @@ defaultConfig:
dbUsername: immich dbUsername: immich
domain: immich.{{ .cloud.domain }} domain: immich.{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
defaultSecrets:
- key: dbPassword
requiredSecrets: requiredSecrets:
- apps.immich.dbPassword - redis.password
- apps.postgres.password - postgres.password
- apps.redis.password

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: immich name: "{{ .namespace }}"

View File

@@ -9,7 +9,7 @@ spec:
- ReadWriteOnce - ReadWriteOnce
resources: resources:
requests: requests:
storage: {{ .apps.immich.storage }} storage: {{ .storage }}
--- ---
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
@@ -21,4 +21,4 @@ spec:
- ReadWriteOnce - ReadWriteOnce
resources: resources:
requests: requests:
storage: {{ .apps.immich.cacheStorage }} storage: {{ .cacheStorage }}

View File

@@ -9,7 +9,7 @@ metadata:
spec: spec:
ports: ports:
- port: 3001 - port: 3001
targetPort: {{ .apps.immich.serverPort }} targetPort: {{ .serverPort }}
selector: selector:
app: immich app: immich
component: server component: server
@@ -25,7 +25,7 @@ metadata:
app: immich-machine-learning app: immich-machine-learning
spec: spec:
ports: ports:
- port: {{ .apps.immich.mlPort }} - port: {{ .mlPort }}
selector: selector:
app: immich app: immich
component: machine-learning component: machine-learning

View File

@@ -26,23 +26,23 @@ spec:
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
env: env:
- name: PGHOST - name: PGHOST
value: {{ .apps.keila.dbHostname }} value: {{ .dbHostname }}
- name: PGUSER - name: PGUSER
value: postgres value: postgres
- name: PGPASSWORD - name: PGPASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: keila-secrets name: keila-secrets
key: apps.postgres.password key: postgres.password
- name: DB_NAME - name: DB_NAME
value: {{ .apps.keila.dbName }} value: {{ .dbName }}
- name: DB_USER - name: DB_USER
value: {{ .apps.keila.dbUsername }} value: {{ .dbUsername }}
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: keila-secrets name: keila-secrets
key: apps.keila.dbPassword key: dbPassword
command: command:
- /bin/bash - /bin/bash
- -c - -c

View File

@@ -14,54 +14,54 @@ spec:
spec: spec:
containers: containers:
- name: keila - name: keila
image: {{ .apps.keila.image }} image: "{{ .image }}"
ports: ports:
- containerPort: {{ .apps.keila.port }} - containerPort: {{ .port }}
env: env:
- name: DB_URL - name: DB_URL
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: keila-secrets name: keila-secrets
key: apps.keila.dbUrl key: dbUrl
- name: URL_HOST - name: URL_HOST
value: {{ .apps.keila.domain }} value: "{{ .domain }}"
- name: URL_SCHEMA - name: URL_SCHEMA
value: https value: https
- name: URL_PORT - name: URL_PORT
value: "443" value: "443"
- name: PORT - name: PORT
value: "{{ .apps.keila.port }}" value: "{{ .port }}"
- name: SECRET_KEY_BASE - name: SECRET_KEY_BASE
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: keila-secrets name: keila-secrets
key: apps.keila.secretKeyBase key: secretKeyBase
- name: MAILER_SMTP_HOST - name: MAILER_SMTP_HOST
value: {{ .apps.keila.smtp.host }} value: "{{ .smtp.host }}"
- name: MAILER_SMTP_PORT - name: MAILER_SMTP_PORT
value: "{{ .apps.keila.smtp.port }}" value: "{{ .smtp.port }}"
- name: MAILER_ENABLE_SSL - name: MAILER_ENABLE_SSL
value: "{{ .apps.keila.smtp.tls }}" value: "{{ .smtp.tls }}"
- name: MAILER_ENABLE_STARTTLS - name: MAILER_ENABLE_STARTTLS
value: "{{ .apps.keila.smtp.startTls }}" value: "{{ .smtp.startTls }}"
- name: MAILER_SMTP_USER - name: MAILER_SMTP_USER
value: {{ .apps.keila.smtp.user }} value: "{{ .smtp.user }}"
- name: MAILER_SMTP_PASSWORD - name: MAILER_SMTP_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: keila-secrets name: keila-secrets
key: apps.keila.smtpPassword key: smtpPassword
- name: MAILER_SMTP_FROM_EMAIL - name: MAILER_SMTP_FROM_EMAIL
value: {{ .apps.keila.smtp.from }} value: "{{ .smtp.from }}"
- name: DISABLE_REGISTRATION - name: DISABLE_REGISTRATION
value: "{{ .apps.keila.disableRegistration }}" value: "{{ .disableRegistration }}"
- name: KEILA_USER - name: KEILA_USER
value: "{{ .apps.keila.adminUser }}" value: "{{ .adminUser }}"
- name: KEILA_PASSWORD - name: KEILA_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: keila-secrets name: keila-secrets
key: apps.keila.adminPassword key: adminPassword
- name: USER_CONTENT_DIR - name: USER_CONTENT_DIR
value: /var/lib/keila/uploads value: /var/lib/keila/uploads
volumeMounts: volumeMounts:
@@ -70,13 +70,13 @@ spec:
livenessProbe: livenessProbe:
httpGet: httpGet:
path: / path: /
port: {{ .apps.keila.port }} port: {{ .port }}
initialDelaySeconds: 30 initialDelaySeconds: 30
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: / path: /
port: {{ .apps.keila.port }} port: {{ .port }}
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 5 periodSeconds: 5
volumes: volumes:

View File

@@ -5,12 +5,12 @@ metadata:
annotations: annotations:
traefik.ingress.kubernetes.io/router.tls: "true" traefik.ingress.kubernetes.io/router.tls: "true"
traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }} external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false" external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
traefik.ingress.kubernetes.io/router.middlewares: keila-cors@kubernetescrd traefik.ingress.kubernetes.io/router.middlewares: keila-cors@kubernetescrd
spec: spec:
rules: rules:
- host: {{ .apps.keila.domain }} - host: {{ .domain }}
http: http:
paths: paths:
- path: / - path: /
@@ -23,4 +23,4 @@ spec:
tls: tls:
- secretName: "wildcard-wild-cloud-tls" - secretName: "wildcard-wild-cloud-tls"
hosts: hosts:
- "{{ .apps.keila.domain }}" - "{{ .domain }}"

View File

@@ -1,15 +1,18 @@
name: keila name: keila
description: Keila is an open-source email marketing platform that allows you to send newsletters and manage mailing lists with privacy and control. description: Keila is an open-source email marketing platform that allows you to send newsletters and manage mailing lists with privacy and control.
version: 1.0.0 version: 0.17.1
icon: https://www.keila.io/images/logo.svg icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/keila.svg
requires: requires:
- name: postgres - name: postgres
defaultConfig: defaultConfig:
image: pentacent/keila:latest namespace: keila
externalDnsDomain: "{{ .cloud.domain }}"
image: pentacent/keila:0.17.1
port: 4000 port: 4000
storage: 1Gi storage: 1Gi
domain: keila.{{ .cloud.domain }} domain: keila.{{ .cloud.domain }}
dbHostname: postgres.postgres.svc.cluster.local dbHostname: "{{ .apps.postgres.host }}"
dbPort: "{{ .apps.postgres.port }}"
dbName: keila dbName: keila
dbUsername: keila dbUsername: keila
disableRegistration: "true" disableRegistration: "true"
@@ -20,12 +23,15 @@ defaultConfig:
port: "{{ .cloud.smtp.port }}" port: "{{ .cloud.smtp.port }}"
from: "{{ .cloud.smtp.from }}" from: "{{ .cloud.smtp.from }}"
user: "{{ .cloud.smtp.user }}" user: "{{ .cloud.smtp.user }}"
tls: {{ .cloud.smtp.tls }} tls: "{{ .cloud.smtp.tls }}"
startTls: {{ .cloud.smtp.startTls }} startTls: "{{ .cloud.smtp.startTls }}"
defaultSecrets:
- key: secretKeyBase
default: "{{ random.AlphaNum 64 }}"
- key: dbPassword
- key: dbUrl
default: "postgres://{{ .app.dbUsername }}:{{ .secrets.dbPassword }}@{{ .app.dbHostname }}:{{ .app.dbPort }}/keila?sslmode=disable"
- key: adminPassword
- key: smtpPassword
requiredSecrets: requiredSecrets:
- apps.keila.secretKeyBase - postgres.password
- apps.keila.dbPassword
- apps.keila.dbUrl
- apps.keila.adminPassword
- apps.keila.smtpPassword
- apps.postgres.password

View File

@@ -21,8 +21,8 @@ spec:
- "OPTIONS" - "OPTIONS"
accessControlAllowOriginList: accessControlAllowOriginList:
- "http://localhost:1313" - "http://localhost:1313"
- "https://*.{{ .cloud.domain }}" - "https://*.{{ .externalDnsDomain }}"
- "https://{{ .cloud.domain }}" - "https://{{ .externalDnsDomain }}"
accessControlExposeHeaders: accessControlExposeHeaders:
- "*" - "*"
accessControlMaxAge: 86400 accessControlMaxAge: 86400

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: keila name: "{{ .namespace }}"

View File

@@ -7,4 +7,4 @@ spec:
- ReadWriteOnce - ReadWriteOnce
resources: resources:
requests: requests:
storage: {{ .apps.keila.storage }} storage: {{ .storage }}

View File

@@ -7,5 +7,5 @@ spec:
component: web component: web
ports: ports:
- port: 80 - port: 80
targetPort: {{ .apps.keila.port }} targetPort: {{ .port }}
protocol: TCP protocol: TCP

View File

@@ -28,23 +28,23 @@ spec:
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
env: env:
- name: PGHOST - name: PGHOST
value: {{ .apps.listmonk.dbHost }} value: {{ .dbHost }}
- name: PGUSER - name: PGUSER
value: postgres value: postgres
- name: PGPASSWORD - name: PGPASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: listmonk-secrets name: listmonk-secrets
key: apps.postgres.password key: postgres.password
- name: DB_NAME - name: DB_NAME
value: {{ .apps.listmonk.dbName }} value: {{ .dbName }}
- name: DB_USER - name: DB_USER
value: {{ .apps.listmonk.dbUser }} value: {{ .dbUser }}
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: listmonk-secrets name: listmonk-secrets
key: apps.listmonk.dbPassword key: dbPassword
command: command:
- /bin/bash - /bin/bash
- -c - -c

View File

@@ -30,21 +30,23 @@ spec:
env: env:
- name: LISTMONK_app__address - name: LISTMONK_app__address
value: "0.0.0.0:9000" value: "0.0.0.0:9000"
- name: LISTMONK_app__root_url
value: "{{ .rootUrl }}"
- name: LISTMONK_db__host - name: LISTMONK_db__host
value: {{ .apps.listmonk.dbHost }} value: {{ .dbHost }}
- name: LISTMONK_db__port - name: LISTMONK_db__port
value: "{{ .apps.listmonk.dbPort }}" value: "{{ .dbPort }}"
- name: LISTMONK_db__user - name: LISTMONK_db__user
value: {{ .apps.listmonk.dbUser }} value: {{ .dbUser }}
- name: LISTMONK_db__database - name: LISTMONK_db__database
value: {{ .apps.listmonk.dbName }} value: {{ .dbName }}
- name: LISTMONK_db__ssl_mode - name: LISTMONK_db__ssl_mode
value: {{ .apps.listmonk.dbSSLMode }} value: {{ .dbSSLMode }}
- name: LISTMONK_db__password - name: LISTMONK_db__password
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: listmonk-secrets name: listmonk-secrets
key: apps.listmonk.dbPassword key: dbPassword
resources: resources:
limits: limits:
cpu: 500m cpu: 500m

View File

@@ -6,16 +6,16 @@ metadata:
annotations: annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true" traefik.ingress.kubernetes.io/router.tls: "true"
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }} external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false" external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
spec: spec:
ingressClassName: traefik ingressClassName: traefik
tls: tls:
- hosts: - hosts:
- {{ .apps.listmonk.domain }} - {{ .domain }}
secretName: {{ .apps.listmonk.tlsSecretName }} secretName: {{ .tlsSecretName }}
rules: rules:
- host: {{ .apps.listmonk.domain }} - host: {{ .domain }}
http: http:
paths: paths:
- path: / - path: /

View File

@@ -1,11 +1,15 @@
name: listmonk name: listmonk
description: Listmonk is a standalone, self-hosted, newsletter and mailing list manager. It is fast, feature-rich, and packed into a single binary. description: Listmonk is a standalone, self-hosted, newsletter and mailing list manager.
It is fast, feature-rich, and packed into a single binary.
version: 5.0.3 version: 5.0.3
icon: https://listmonk.app/static/images/logo.svg icon: https://listmonk.app/static/images/logo.svg
requires: requires:
- name: postgres - name: postgres
defaultConfig: defaultConfig:
namespace: listmonk
externalDnsDomain: '{{ .cloud.domain }}'
domain: listmonk.{{ .cloud.domain }} domain: listmonk.{{ .cloud.domain }}
rootUrl: https://listmonk.{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
storage: 1Gi storage: 1Gi
dbHost: postgres.postgres.svc.cluster.local dbHost: postgres.postgres.svc.cluster.local
@@ -14,7 +18,9 @@ defaultConfig:
dbUser: listmonk dbUser: listmonk
dbSSLMode: disable dbSSLMode: disable
timezone: UTC timezone: UTC
defaultSecrets:
- key: dbPassword
- key: dbUrl
default: 'postgres://{{ .app.dbUser }}:{{ .secrets.dbPassword }}@{{ .app.dbHost }}:{{ .app.dbPort }}/{{ .app.dbName }}?sslmode={{ .app.dbSSLMode }}'
requiredSecrets: requiredSecrets:
- apps.listmonk.dbPassword - postgres.password
- apps.listmonk.dbUrl
- apps.postgres.password

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: listmonk name: "{{ .namespace }}"

View File

@@ -8,4 +8,4 @@ spec:
- ReadWriteOnce - ReadWriteOnce
resources: resources:
requests: requests:
storage: {{ .apps.listmonk.storage }} storage: {{ .storage }}

64
loomio/db-init-job.yaml Normal file
View File

@@ -0,0 +1,64 @@
apiVersion: batch/v1
kind: Job
metadata:
name: loomio-db-init
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: db-init
image: postgres:15-alpine
env:
- name: PGHOST
value: "{{ .db.host }}"
- name: PGPORT
value: "{{ .db.port }}"
- name: PGUSER
value: postgres
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: postgres-secrets
key: postgres.password
- name: LOOMIO_DB_NAME
value: "{{ .db.name }}"
- name: LOOMIO_DB_USER
value: "{{ .db.user }}"
- name: LOOMIO_DB_PASSWORD
valueFrom:
secretKeyRef:
name: loomio-secrets
key: dbPassword
command:
- sh
- -c
- |
echo "Creating database and user for Loomio..."
# Check if database exists, create if not
psql -tc "SELECT 1 FROM pg_database WHERE datname = '$LOOMIO_DB_NAME'" | grep -q 1 || \
psql -c "CREATE DATABASE \"$LOOMIO_DB_NAME\""
# Check if user exists, create or update password
psql -tc "SELECT 1 FROM pg_user WHERE usename = '$LOOMIO_DB_USER'" | grep -q 1 && \
psql -c "ALTER USER \"$LOOMIO_DB_USER\" WITH PASSWORD '$LOOMIO_DB_PASSWORD'" || \
psql -c "CREATE USER \"$LOOMIO_DB_USER\" WITH PASSWORD '$LOOMIO_DB_PASSWORD'"
# Grant all privileges
psql -c "GRANT ALL PRIVILEGES ON DATABASE \"$LOOMIO_DB_NAME\" TO \"$LOOMIO_DB_USER\""
# Connect to the database and grant schema permissions
psql -d "$LOOMIO_DB_NAME" -c "GRANT ALL ON SCHEMA public TO \"$LOOMIO_DB_USER\""
echo "Database initialization complete!"
securityContext:
runAsNonRoot: true
runAsUser: 999 # postgres user
runAsGroup: 999
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: true
seccompProfile:
type: RuntimeDefault

View File

@@ -0,0 +1,101 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: loomio-worker
spec:
replicas: 1
selector:
matchLabels:
component: worker
template:
metadata:
labels:
component: worker
spec:
containers:
- name: worker
image: {{ .workerImage }}
env:
- name: TASK
value: worker
- name: RAILS_ENV
value: production
- name: SITE_NAME
value: {{ .appName }}
- name: CANONICAL_HOST
value: {{ .domain }}
- name: PUBLIC_APP_URL
value: https://{{ .domain }}
- name: SUPPORT_EMAIL
value: {{ .supportEmail }}
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: loomio-secrets
key: dbUrl
- name: REDIS_URL
value: {{ .redisUrl }}
- name: DEVISE_SECRET
valueFrom:
secretKeyRef:
name: loomio-secrets
key: deviseSecret
- name: SECRET_COOKIE_TOKEN
valueFrom:
secretKeyRef:
name: loomio-secrets
key: secretCookieToken
- name: ACTIVE_STORAGE_SERVICE
value: {{ .activeStorageService }}
- name: SMTP_AUTH
value: {{ .smtp.auth }}
- name: SMTP_DOMAIN
value: {{ .smtp.domain }}
- name: SMTP_SERVER
value: {{ .smtp.host }}
- name: SMTP_PORT
value: "{{ .smtp.port }}"
- name: SMTP_USERNAME
value: {{ .smtp.user }}
- name: SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: loomio-secrets
key: smtpPassword
- name: SMTP_USE_SSL
value: "{{ .smtp.tls }}"
- name: REPLY_HOSTNAME
value: {{ .smtp.from }}
volumeMounts:
- name: uploads
mountPath: /loomio/public/system
- name: storage
mountPath: /loomio/storage
- name: tmp
mountPath: /loomio/tmp
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 1Gi
cpu: 500m
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false
seccompProfile:
type: RuntimeDefault
volumes:
- name: uploads
persistentVolumeClaim:
claimName: loomio-uploads
- name: storage
persistentVolumeClaim:
claimName: loomio-storage
- name: tmp
emptyDir: {}

124
loomio/deployment.yaml Normal file
View File

@@ -0,0 +1,124 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: loomio
spec:
replicas: 1
selector:
matchLabels:
component: web
template:
metadata:
labels:
component: web
spec:
containers:
- name: loomio
image: {{ .image }}
ports:
- containerPort: 3000
name: http
env:
- name: RAILS_ENV
value: production
- name: SITE_NAME
value: {{ .appName }}
- name: CANONICAL_HOST
value: {{ .domain }}
- name: PUBLIC_APP_URL
value: https://{{ .domain }}
- name: SUPPORT_EMAIL
value: {{ .supportEmail }}
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: loomio-secrets
key: dbUrl
- name: REDIS_URL
value: {{ .redisUrl }}
- name: DEVISE_SECRET
valueFrom:
secretKeyRef:
name: loomio-secrets
key: deviseSecret
- name: SECRET_COOKIE_TOKEN
valueFrom:
secretKeyRef:
name: loomio-secrets
key: secretCookieToken
- name: FORCE_SSL
value: "{{ .forceSSL }}"
- name: USE_RACK_ATTACK
value: "{{ .useRackAttack }}"
- name: PUMA_WORKERS
value: "{{ .pumaWorkers }}"
- name: MIN_THREADS
value: "{{ .minThreads }}"
- name: MAX_THREADS
value: "{{ .maxThreads }}"
- name: ACTIVE_STORAGE_SERVICE
value: {{ .activeStorageService }}
- name: SMTP_AUTH
value: {{ .smtp.auth }}
- name: SMTP_DOMAIN
value: {{ .smtp.domain }}
- name: SMTP_SERVER
value: {{ .smtp.host }}
- name: SMTP_PORT
value: "{{ .smtp.port }}"
- name: SMTP_USERNAME
value: {{ .smtp.user }}
- name: SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: loomio-secrets
key: smtpPassword
- name: SMTP_USE_SSL
value: "{{ .smtp.tls }}"
- name: REPLY_HOSTNAME
value: {{ .smtp.from }}
volumeMounts:
- name: uploads
mountPath: /loomio/public/system
- name: storage
mountPath: /loomio/storage
- name: tmp
mountPath: /loomio/tmp
resources:
requests:
memory: 512Mi
cpu: 200m
limits:
memory: 2Gi
cpu: 1000m
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false
seccompProfile:
type: RuntimeDefault
volumes:
- name: uploads
persistentVolumeClaim:
claimName: loomio-uploads
- name: storage
persistentVolumeClaim:
claimName: loomio-storage
- name: tmp
emptyDir: {}

24
loomio/ingress.yaml Normal file
View File

@@ -0,0 +1,24 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: loomio
annotations:
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
spec:
ingressClassName: traefik
tls:
- hosts:
- {{ .domain }}
secretName: {{ .tlsSecretName }}
rules:
- host: {{ .domain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: loomio
port:
number: 80

20
loomio/kustomization.yaml Normal file
View File

@@ -0,0 +1,20 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: loomio
resources:
- namespace.yaml
- pvc-uploads.yaml
- pvc-storage.yaml
- deployment.yaml
- deployment-worker.yaml
- service.yaml
- ingress.yaml
- db-init-job.yaml
labels:
- includeSelectors: true
pairs:
app: loomio
managedBy: kustomize
partOf: wild-cloud

55
loomio/manifest.yaml Normal file
View File

@@ -0,0 +1,55 @@
name: loomio
description: Loomio is a collaborative decision-making tool that makes it easy for groups to make decisions together
version: 3.0.11
icon: https://www.loomio.com/brand/logo_gold.svg
requires:
- name: postgres
installed_as: postgres
- name: redis
defaultConfig:
namespace: loomio
externalDnsDomain: "{{ .cloud.domain }}"
image: loomio/loomio:v3.0.11
workerImage: loomio/loomio:v3.0.11
appName: Loomio
domain: "loomio.{{ .cloud.domain }}"
tlsSecretName: wildcard-wild-cloud-tls
port: 3000
storage:
uploads: 5Gi
files: 5Gi
plugins: 1Gi
redisUrl: "{{ .apps.redis.uri }}"
adminEmail: "{{ .operator.email }}"
supportEmail: "{{ .operator.email }}"
forceSSL: "1"
useRackAttack: "1"
pumaWorkers: "2"
minThreads: "5"
maxThreads: "5"
activeStorageService: local
db:
name: loomio
user: loomio
host: "{{ .apps.postgres.host }}"
port: "{{ .apps.postgres.port }}"
smtp:
auth: plain
domain: "{{ .cloud.domain }}"
host: "{{ .cloud.smtp.host }}"
port: "{{ .cloud.smtp.port }}"
user: "{{ .cloud.smtp.user }}"
tls: "{{ .cloud.smtp.tls }}"
from: "{{ .cloud.smtp.from }}"
defaultSecrets:
- key: dbPassword
default: "{{ random.AlphaNum 32 }}"
- key: dbUrl
default: "postgresql://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?pool=30"
- key: deviseSecret
default: "{{ random.AlphaNum 32 }}"
- key: secretCookieToken
default: "{{ random.AlphaNum 32 }}"
- key: smtpPassword
requiredSecrets:
- postgres.password

4
loomio/namespace.yaml Normal file
View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: {{ .namespace }}

11
loomio/pvc-storage.yaml Normal file
View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: loomio-storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .storage.files }}
storageClassName: longhorn

11
loomio/pvc-uploads.yaml Normal file
View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: loomio-uploads
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .storage.uploads }}
storageClassName: longhorn

13
loomio/service.yaml Normal file
View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: loomio
spec:
type: ClusterIP
selector:
component: web
ports:
- name: http
port: 80
targetPort: 3000
protocol: TCP

View File

@@ -3,7 +3,7 @@ kind: Deployment
metadata: metadata:
name: memcached name: memcached
spec: spec:
replicas: {{ .apps.memcached.replicas }} replicas: {{ .replicas }}
selector: selector:
matchLabels: matchLabels:
component: cache component: cache
@@ -14,24 +14,24 @@ spec:
spec: spec:
containers: containers:
- name: memcached - name: memcached
image: {{ .apps.memcached.image }} image: "{{ .image }}"
ports: ports:
- containerPort: {{ .apps.memcached.port }} - containerPort: {{ .port }}
name: memcached name: memcached
args: args:
- -m - -m
- {{ .apps.memcached.memoryLimit }} - "{{ .memoryLimit }}"
- -c - -c
- "{{ .apps.memcached.maxConnections }}" - "{{ .maxConnections }}"
- -p - -p
- "{{ .apps.memcached.port }}" - "{{ .port }}"
resources: resources:
requests: requests:
memory: {{ .apps.memcached.resources.requests.memory }} memory: "{{ .resources.requests.memory }}"
cpu: {{ .apps.memcached.resources.requests.cpu }} cpu: "{{ .resources.requests.cpu }}"
limits: limits:
memory: {{ .apps.memcached.resources.limits.memory }} memory: "{{ .resources.limits.memory }}"
cpu: {{ .apps.memcached.resources.limits.cpu }} cpu: "{{ .resources.limits.cpu }}"
securityContext: securityContext:
runAsNonRoot: true runAsNonRoot: true
runAsUser: 11211 runAsUser: 11211

View File

@@ -1,9 +1,11 @@
name: memcached name: memcached
description: Memcached is an in-memory key-value store for small chunks of arbitrary data, commonly used as a cache layer. description: Memcached is an in-memory key-value store for small chunks of arbitrary
data, commonly used as a cache layer.
version: 1.6.32 version: 1.6.32
icon: https://memcached.org/memcached-logo.png icon: https://www.vectorlogo.zone/logos/memcached/memcached-icon.svg
requires: [] requires: []
defaultConfig: defaultConfig:
namespace: memcached
image: memcached:1.6.32-alpine image: memcached:1.6.32-alpine
port: 11211 port: 11211
memoryLimit: 64m memoryLimit: 64m
@@ -16,4 +18,4 @@ defaultConfig:
limits: limits:
memory: 128Mi memory: 128Mi
cpu: 200m cpu: 200m
requiredSecrets: [] defaultSecrets: []

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: memcached name: "{{ .namespace }}"

View File

@@ -4,8 +4,8 @@ metadata:
name: memcached name: memcached
spec: spec:
ports: ports:
- port: {{ .apps.memcached.port }} - port: {{ .port }}
targetPort: {{ .apps.memcached.port }} targetPort: {{ .port }}
protocol: TCP protocol: TCP
name: memcached name: memcached
selector: selector:

View File

@@ -4,6 +4,8 @@ version: 9.1.0
icon: https://www.mysql.com/common/logos/logo-mysql-170x115.png icon: https://www.mysql.com/common/logos/logo-mysql-170x115.png
requires: [] requires: []
defaultConfig: defaultConfig:
namespace: mysql
externalDnsDomain: '{{ .cloud.domain }}'
image: mysql:9.1.0 image: mysql:9.1.0
port: 3306 port: 3306
storage: 20Gi storage: 20Gi
@@ -12,6 +14,6 @@ defaultConfig:
user: mysql user: mysql
timezone: UTC timezone: UTC
enableSSL: false enableSSL: false
requiredSecrets: defaultSecrets:
- apps.mysql.rootPassword - key: rootPassword
- apps.mysql.password - key: password

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: mysql name: "{{ .namespace }}"

View File

@@ -9,7 +9,7 @@ spec:
publishNotReadyAddresses: true publishNotReadyAddresses: true
ports: ports:
- name: mysql - name: mysql
port: {{ .apps.mysql.port }} port: {{ .port }}
protocol: TCP protocol: TCP
targetPort: mysql targetPort: mysql
selector: selector:

View File

@@ -7,7 +7,7 @@ spec:
type: ClusterIP type: ClusterIP
ports: ports:
- name: mysql - name: mysql
port: {{ .apps.mysql.port }} port: {{ .port }}
protocol: TCP protocol: TCP
targetPort: mysql targetPort: mysql
selector: selector:

View File

@@ -29,7 +29,7 @@ spec:
type: RuntimeDefault type: RuntimeDefault
containers: containers:
- name: mysql - name: mysql
image: {{ .apps.mysql.image }} image: {{ .image }}
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
@@ -42,21 +42,21 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: mysql-secrets name: mysql-secrets
key: apps.mysql.rootPassword key: rootPassword
- name: MYSQL_USER - name: MYSQL_USER
value: {{ .apps.mysql.user }} value: {{ .user }}
- name: MYSQL_PASSWORD - name: MYSQL_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: mysql-secrets name: mysql-secrets
key: apps.mysql.password key: password
- name: MYSQL_DATABASE - name: MYSQL_DATABASE
value: {{ .apps.mysql.dbName }} value: {{ .dbName }}
- name: TZ - name: TZ
value: {{ .apps.mysql.timezone }} value: {{ .timezone }}
ports: ports:
- name: mysql - name: mysql
containerPort: {{ .apps.mysql.port }} containerPort: {{ .port }}
protocol: TCP protocol: TCP
livenessProbe: livenessProbe:
exec: exec:
@@ -113,4 +113,4 @@ spec:
- ReadWriteOnce - ReadWriteOnce
resources: resources:
requests: requests:
storage: {{ .apps.mysql.storage }} storage: {{ .storage }}

View File

@@ -45,7 +45,7 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: open-webui-secrets name: open-webui-secrets
key: apps.openWebui.secretKey key: openWebui.secretKey
volumeMounts: volumeMounts:
- name: data - name: data
mountPath: /app/backend/data mountPath: /app/backend/data

View File

@@ -4,12 +4,12 @@ kind: Ingress
metadata: metadata:
name: open-webui name: open-webui
annotations: annotations:
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }} external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false" external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
traefik.ingress.kubernetes.io/router.middlewares: crowdsec-crowdsec-bouncer@kubernetescrd,crowdsec-rate-limit@kubernetescrd traefik.ingress.kubernetes.io/router.middlewares: crowdsec-crowdsec-bouncer@kubernetescrd,crowdsec-rate-limit@kubernetescrd
spec: spec:
rules: rules:
- host: {{ .apps.open-webui.domain }} - host: {{ .domain }}
http: http:
paths: paths:
- path: / - path: /
@@ -22,4 +22,4 @@ spec:
tls: tls:
- secretName: wildcard-wild-cloud-tls - secretName: wildcard-wild-cloud-tls
hosts: hosts:
- {{ .apps.open-webui.domain }} - {{ .domain }}

View File

@@ -1,17 +1,18 @@
name: openWebui name: openWebui
description: Open WebUI is a comprehensive, open-source web interface for AI models. Features a user-friendly design, supports various LLM runners, and operates entirely offline. Perfect for creating a ChatGPT-like experience with local or hosted models. description: Open WebUI is a comprehensive, open-source web interface for AI models.
Features a user-friendly design, supports various LLM runners, and operates entirely
offline. Perfect for creating a ChatGPT-like experience with local or hosted models.
version: 0.4.5 version: 0.4.5
icon: https://docs.openwebui.com/assets/logo-dark.png icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/open-webui.svg
requires: [] requires: []
defaultConfig: defaultConfig:
namespace: open-webui
externalDnsDomain: '{{ .cloud.domain }}'
image: ghcr.io/open-webui/open-webui:main image: ghcr.io/open-webui/open-webui:main
port: 8080 port: 8080
storage: 10Gi storage: 10Gi
domain: chat.{{ .cloud.domain }} domain: chat.{{ .cloud.domain }}
# vLLM integration - connect to existing vLLM service
vllmApiUrl: http://vllm-service.llm.svc.cluster.local:8000/v1 vllmApiUrl: http://vllm-service.llm.svc.cluster.local:8000/v1
# Authentication settings
enableAuth: true enableAuth: true
enableSignup: false enableSignup: false
requiredSecrets: defaultSecrets: []
- apps.openWebui.secretKey

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: open-webui name: "{{ .namespace }}"

View File

@@ -5,17 +5,17 @@ kind: "ConfigMap"
metadata: metadata:
name: "openproject-core" name: "openproject-core"
data: data:
DATABASE_HOST: "{{ .apps.openproject.dbHostname }}" DATABASE_HOST: "{{ .dbHostname }}"
DATABASE_PORT: "5432" DATABASE_PORT: "5432"
DATABASE_URL: "postgresql://{{ .apps.openproject.dbUsername }}@{{ .apps.openproject.dbHostname }}:5432/{{ .apps.openproject.dbName }}" DATABASE_URL: "postgresql://{{ .dbUsername }}@{{ .dbHostname }}:5432/{{ .dbName }}"
OPENPROJECT_SEED_ADMIN_USER_PASSWORD_RESET: "{{ .apps.openproject.adminPasswordReset }}" OPENPROJECT_SEED_ADMIN_USER_PASSWORD_RESET: "{{ .adminPasswordReset }}"
OPENPROJECT_SEED_ADMIN_USER_NAME: "{{ .apps.openproject.adminUserName }}" OPENPROJECT_SEED_ADMIN_USER_NAME: "{{ .adminUserName }}"
OPENPROJECT_SEED_ADMIN_USER_MAIL: "{{ .apps.openproject.adminUserEmail }}" OPENPROJECT_SEED_ADMIN_USER_MAIL: "{{ .adminUserEmail }}"
OPENPROJECT_HTTPS: "{{ .apps.openproject.https }}" OPENPROJECT_HTTPS: "{{ .https }}"
OPENPROJECT_SEED_LOCALE: "{{ .apps.openproject.seedLocale }}" OPENPROJECT_SEED_LOCALE: "{{ .seedLocale }}"
OPENPROJECT_HOST__NAME: "{{ .apps.openproject.domain }}" OPENPROJECT_HOST__NAME: "{{ .domain }}"
OPENPROJECT_HSTS: "{{ .apps.openproject.hsts }}" OPENPROJECT_HSTS: "{{ .hsts }}"
OPENPROJECT_RAILS__CACHE__STORE: "{{ .apps.openproject.cacheStore }}" OPENPROJECT_RAILS__CACHE__STORE: "{{ .cacheStore }}"
OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "{{ .apps.openproject.railsRelativeUrlRoot }}" OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "{{ .railsRelativeUrlRoot }}"
POSTGRES_STATEMENT_TIMEOUT: "{{ .apps.openproject.postgresStatementTimeout }}" POSTGRES_STATEMENT_TIMEOUT: "{{ .postgresStatementTimeout }}"
... ...

View File

@@ -5,5 +5,5 @@ kind: "ConfigMap"
metadata: metadata:
name: "openproject-memcached" name: "openproject-memcached"
data: data:
OPENPROJECT_CACHE__MEMCACHE__SERVER: "{{ .apps.openproject.memcachedHostname }}:{{ .apps.openproject.memcachedPort }}" OPENPROJECT_CACHE__MEMCACHE__SERVER: "{{ .memcachedHostname }}:{{ .memcachedPort }}"
... ...

View File

@@ -12,7 +12,7 @@ spec:
spec: spec:
containers: containers:
- name: db-init - name: db-init
image: {{ .apps.postgres.image }} image: postgres:17
command: ["/bin/bash", "-c"] command: ["/bin/bash", "-c"]
args: args:
- | - |
@@ -36,16 +36,16 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: postgres-secrets name: postgres-secrets
key: apps.postgres.password key: password
- name: DB_HOSTNAME - name: DB_HOSTNAME
value: "{{ .apps.openproject.dbHostname }}" value: "{{ .dbHostname }}"
- name: DB_DATABASE_NAME - name: DB_DATABASE_NAME
value: "{{ .apps.openproject.dbName }}" value: "{{ .dbName }}"
- name: DB_USERNAME - name: DB_USERNAME
value: "{{ .apps.openproject.dbUsername }}" value: "{{ .dbUsername }}"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: openproject-secrets name: openproject-secrets
key: apps.openproject.dbPassword key: dbPassword
restartPolicy: OnFailure restartPolicy: OnFailure

View File

@@ -7,10 +7,10 @@ metadata:
spec: spec:
tls: tls:
- hosts: - hosts:
- "{{ .apps.openproject.domain }}" - "{{ .domain }}"
secretName: "wildcard-wild-cloud-tls" secretName: "wildcard-wild-cloud-tls"
rules: rules:
- host: "{{ .apps.openproject.domain }}" - host: "{{ .domain }}"
http: http:
paths: paths:
- path: / - path: /

View File

@@ -1,11 +1,14 @@
name: openproject name: openproject
description: OpenProject is an open-source project management software that provides comprehensive features for project planning, tracking, and collaboration. description: OpenProject is an open-source project management software that provides
comprehensive features for project planning, tracking, and collaboration.
version: 16.1.1 version: 16.1.1
icon: https://www.openproject.org/assets/images/openproject-logo.png icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/openproject.svg
requires: requires:
- name: postgres - name: postgres
- name: memcached - name: memcached
defaultConfig: defaultConfig:
namespace: openproject
externalDnsDomain: '{{ .cloud.domain }}'
serverImage: openproject/openproject:16.1.1-slim serverImage: openproject/openproject:16.1.1-slim
timezone: UTC timezone: UTC
serverPort: 8080 serverPort: 8080
@@ -20,14 +23,15 @@ defaultConfig:
hsts: true hsts: true
seedLocale: en seedLocale: en
adminUserName: OpenProject Admin adminUserName: OpenProject Admin
adminUserEmail: "{{ .operator.email }}" adminUserEmail: '{{ .operator.email }}'
adminPasswordReset: true adminPasswordReset: true
postgresStatementTimeout: 120s postgresStatementTimeout: 120s
tmpVolumesStorage: 2Gi tmpVolumesStorage: 2Gi
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
cacheStore: memcache cacheStore: memcache
railsRelativeUrlRoot: "" railsRelativeUrlRoot: ''
defaultSecrets:
- key: dbPassword
- key: adminPassword
requiredSecrets: requiredSecrets:
- apps.openproject.dbPassword - postgres.password
- apps.openproject.adminPassword
- apps.postgres.password

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: openproject name: "{{ .namespace }}"

View File

@@ -8,5 +8,5 @@ spec:
accessModes: [ReadWriteMany] accessModes: [ReadWriteMany]
resources: resources:
requests: requests:
storage: "{{ .apps.openproject.storage }}" storage: "{{ .storage }}"
... ...

View File

@@ -27,7 +27,7 @@ spec:
accessModes: ["ReadWriteOnce"] accessModes: ["ReadWriteOnce"]
resources: resources:
requests: requests:
storage: {{ .apps.openproject.tmpVolumesStorage }} storage: {{ .tmpVolumesStorage }}
- name: app-tmp - name: app-tmp
# we can't use emptyDir due to the sticky bit / world writable issue # we can't use emptyDir due to the sticky bit / world writable issue
# see: https://github.com/kubernetes/kubernetes/issues/110835 # see: https://github.com/kubernetes/kubernetes/issues/110835
@@ -39,13 +39,13 @@ spec:
accessModes: ["ReadWriteOnce"] accessModes: ["ReadWriteOnce"]
resources: resources:
requests: requests:
storage: {{ .apps.openproject.tmpVolumesStorage }} storage: {{ .tmpVolumesStorage }}
- name: "data" - name: "data"
persistentVolumeClaim: persistentVolumeClaim:
claimName: openproject claimName: openproject
initContainers: initContainers:
- name: check-db-ready - name: check-db-ready
image: "{{ .apps.postgres.image }}" image: "postgres:17"
imagePullPolicy: Always imagePullPolicy: Always
command: [ command: [
'sh', 'sh',
@@ -62,12 +62,12 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: openproject-secrets name: openproject-secrets
key: apps.openproject.dbPassword key: dbPassword
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD - name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: openproject-secrets name: openproject-secrets
key: apps.openproject.adminPassword key: adminPassword
resources: resources:
limits: limits:
memory: 200Mi memory: 200Mi
@@ -91,7 +91,7 @@ spec:
type: RuntimeDefault type: RuntimeDefault
containers: containers:
- name: seeder - name: seeder
image: "{{ .apps.openproject.serverImage }}" image: "{{ .serverImage }}"
imagePullPolicy: Always imagePullPolicy: Always
args: args:
- bash - bash
@@ -106,12 +106,12 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: openproject-secrets name: openproject-secrets
key: apps.openproject.dbPassword key: dbPassword
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD - name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: openproject-secrets name: openproject-secrets
key: apps.openproject.adminPassword key: adminPassword
resources: resources:
limits: limits:
memory: 512Mi memory: 512Mi

View File

@@ -43,7 +43,7 @@ spec:
accessModes: ["ReadWriteOnce"] accessModes: ["ReadWriteOnce"]
resources: resources:
requests: requests:
storage: {{ .apps.openproject.tmpVolumesStorage }} storage: {{ .tmpVolumesStorage }}
- name: app-tmp - name: app-tmp
# we can't use emptyDir due to the sticky bit / world writable issue # we can't use emptyDir due to the sticky bit / world writable issue
# see: https://github.com/kubernetes/kubernetes/issues/110835 # see: https://github.com/kubernetes/kubernetes/issues/110835
@@ -55,7 +55,7 @@ spec:
accessModes: ["ReadWriteOnce"] accessModes: ["ReadWriteOnce"]
resources: resources:
requests: requests:
storage: {{ .apps.openproject.tmpVolumesStorage }} storage: {{ .tmpVolumesStorage }}
- name: "data" - name: "data"
persistentVolumeClaim: persistentVolumeClaim:
claimName: openproject claimName: openproject
@@ -72,8 +72,13 @@ spec:
runAsUser: 1000 runAsUser: 1000
seccompProfile: seccompProfile:
type: RuntimeDefault type: RuntimeDefault
image: {{ .apps.openproject.serverImage }} image: postgres:17
imagePullPolicy: Always imagePullPolicy: Always
command: [
'sh',
'-c',
'until pg_isready -h $DATABASE_HOST -p $DATABASE_PORT; do echo "waiting for database $DATABASE_HOST:$DATABASE_PORT"; sleep 2; done; echo "Database is ready!"'
]
envFrom: envFrom:
- configMapRef: - configMapRef:
name: openproject-core name: openproject-core
@@ -84,14 +89,12 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: openproject-secrets name: openproject-secrets
key: apps.openproject.dbPassword key: dbPassword
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD - name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: openproject-secrets name: openproject-secrets
key: apps.openproject.adminPassword key: adminPassword
args:
- /app/docker/prod/wait-for-db
resources: resources:
limits: limits:
memory: 1Gi memory: 1Gi
@@ -115,7 +118,7 @@ spec:
runAsUser: 1000 runAsUser: 1000
seccompProfile: seccompProfile:
type: RuntimeDefault type: RuntimeDefault
image: {{ .apps.openproject.serverImage }} image: {{ .serverImage }}
imagePullPolicy: Always imagePullPolicy: Always
envFrom: envFrom:
- configMapRef: - configMapRef:
@@ -127,12 +130,12 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: openproject-secrets name: openproject-secrets
key: apps.openproject.dbPassword key: dbPassword
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD - name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: openproject-secrets name: openproject-secrets
key: apps.openproject.adminPassword key: adminPassword
args: args:
- /app/docker/prod/web - /app/docker/prod/web
volumeMounts: volumeMounts:

View File

@@ -43,7 +43,7 @@ spec:
accessModes: ["ReadWriteOnce"] accessModes: ["ReadWriteOnce"]
resources: resources:
requests: requests:
storage: {{ .apps.openproject.tmpVolumesStorage }} storage: {{ .tmpVolumesStorage }}
- name: app-tmp - name: app-tmp
# we can't use emptyDir due to the sticky bit / world writable issue # we can't use emptyDir due to the sticky bit / world writable issue
# see: https://github.com/kubernetes/kubernetes/issues/110835 # see: https://github.com/kubernetes/kubernetes/issues/110835
@@ -55,7 +55,7 @@ spec:
accessModes: ["ReadWriteOnce"] accessModes: ["ReadWriteOnce"]
resources: resources:
requests: requests:
storage: {{ .apps.openproject.tmpVolumesStorage }} storage: {{ .tmpVolumesStorage }}
- name: "data" - name: "data"
persistentVolumeClaim: persistentVolumeClaim:
claimName: openproject claimName: openproject
@@ -72,8 +72,13 @@ spec:
runAsUser: 1000 runAsUser: 1000
seccompProfile: seccompProfile:
type: RuntimeDefault type: RuntimeDefault
image: {{ .apps.openproject.serverImage }} image: postgres:17
imagePullPolicy: Always imagePullPolicy: Always
command: [
'sh',
'-c',
'until pg_isready -h $DATABASE_HOST -p $DATABASE_PORT; do echo "waiting for database $DATABASE_HOST:$DATABASE_PORT"; sleep 2; done; echo "Database is ready!"'
]
envFrom: envFrom:
- configMapRef: - configMapRef:
name: openproject-core name: openproject-core
@@ -84,15 +89,12 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: openproject-secrets name: openproject-secrets
key: apps.openproject.dbPassword key: dbPassword
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD - name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: openproject-secrets name: openproject-secrets
key: apps.openproject.adminPassword key: adminPassword
args:
- bash
- /app/docker/prod/wait-for-db
resources: resources:
limits: limits:
memory: 1Gi memory: 1Gi
@@ -116,7 +118,7 @@ spec:
runAsUser: 1000 runAsUser: 1000
seccompProfile: seccompProfile:
type: RuntimeDefault type: RuntimeDefault
image: {{ .apps.openproject.serverImage }} image: {{ .serverImage }}
imagePullPolicy: Always imagePullPolicy: Always
envFrom: envFrom:
- configMapRef: - configMapRef:
@@ -132,7 +134,7 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: openproject-secrets name: openproject-secrets
key: apps.openproject.dbPassword key: dbPassword
- name: "OPENPROJECT_GOOD_JOB_QUEUES" - name: "OPENPROJECT_GOOD_JOB_QUEUES"
value: "" value: ""
volumeMounts: volumeMounts:

View File

@@ -15,7 +15,7 @@ spec:
spec: spec:
containers: containers:
- name: postgres - name: postgres
image: "{{ .apps.postgres.image }}" image: "{{ .image }}"
args: args:
[ [
"-c", "-c",
@@ -35,16 +35,16 @@ spec:
- name: PGDATA - name: PGDATA
value: /var/lib/postgresql/data/pgdata value: /var/lib/postgresql/data/pgdata
- name: TZ - name: TZ
value: "{{ .apps.postgres.timezone }}" value: "{{ .timezone }}"
- name: POSTGRES_DB - name: POSTGRES_DB
value: "{{ .apps.postgres.database }}" value: "{{ .database }}"
- name: POSTGRES_USER - name: POSTGRES_USER
value: "{{ .apps.postgres.user }}" value: "{{ .user }}"
- name: POSTGRES_PASSWORD - name: POSTGRES_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: postgres-secrets name: postgres-secrets
key: apps.postgres.password key: password
volumeMounts: volumeMounts:
- name: postgres-data - name: postgres-data
mountPath: /var/lib/postgresql/data mountPath: /var/lib/postgresql/data

View File

@@ -4,10 +4,13 @@ description: PostgreSQL is a powerful, open source object-relational database sy
version: 1.0.0 version: 1.0.0
icon: https://www.postgresql.org/media/img/about/press/elephant.png icon: https://www.postgresql.org/media/img/about/press/elephant.png
defaultConfig: defaultConfig:
namespace: postgres
host: postgres.postgres.svc.cluster.local
port: 5432
database: postgres database: postgres
user: postgres user: postgres
storage: 10Gi storage: 10Gi
image: pgvector/pgvector:pg15 image: pgvector/pgvector:pg15
timezone: UTC timezone: UTC
requiredSecrets: defaultSecrets:
- apps.postgres.password - key: password

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: postgres name: {{ .namespace }}

View File

@@ -9,4 +9,4 @@ spec:
storageClassName: longhorn storageClassName: longhorn
resources: resources:
requests: requests:
storage: {{ .apps.postgres.storage | default "10Gi" }} storage: {{ .storage }}

View File

@@ -5,6 +5,6 @@ metadata:
name: postgres name: postgres
spec: spec:
ports: ports:
- port: 5432 - port: {{ .port }}
selector: selector:
app: postgres app: postgres

View File

@@ -14,18 +14,18 @@ spec:
app: redis app: redis
spec: spec:
containers: containers:
- image: "{{ .apps.redis.image }}" - image: "{{ .image }}"
name: redis name: redis
ports: ports:
- containerPort: {{ .apps.redis.port }} - containerPort: {{ .port }}
env: env:
- name: TZ - name: TZ
value: "{{ .apps.redis.timezone }}" value: "{{ .timezone }}"
- name: REDIS_PASSWORD - name: REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: redis-secrets name: redis-secrets
key: apps.redis.password key: password
command: command:
- redis-server - redis-server
- --requirepass - --requirepass

View File

@@ -2,10 +2,13 @@ name: redis
install: true install: true
description: Redis is an open source, in-memory data structure store, used as a database, cache and message broker. description: Redis is an open source, in-memory data structure store, used as a database, cache and message broker.
version: 1.0.0 version: 1.0.0
icon: <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 85 27"><path fill="#FF4438" fill-rule="evenodd" d="M75.29 13.813c0-2.968 2.2-4.69 4.947-4.69 2.052 0 3.884.99 4.763 3.444-.257 1.245-1.795 2.638-2.455 2.858-.55-1.173-1.172-1.869-1.758-1.869-.733 0-.77.513-.77 1.172 0 .467.134 1.155.295 1.983.243 1.25.548 2.82.548 4.43 0 2.93-2.052 5.092-5.203 5.092-2.885 0-4.48-1.892-5.19-4.913-1.885 3.377-4.641 4.913-6.754 4.913-3.302 0-4.08-2.441-4-4.917-1.328 2.345-3.882 4.917-6.331 4.917-2.501 0-3.384-2.177-3.182-4.712-1.498 2.791-4.208 4.712-6.82 4.712-2.836 0-4.239-2.252-3.785-5.044-1.907 2.344-5.458 5.044-9.149 5.044-4.209 0-6.04-2.27-6.258-5.114-2.031 3.256-4.77 5.224-8.03 5.224-4.709 0-6.393-4.187-6.638-7.611a111 111 0 0 1-6.113 7.464c-.256.257-.476.403-.732.403C1.832 26.6.11 22.862 0 21.47c.723-1.121 5.281-6.13 8.95-10.161 1.29-1.417 2.471-2.714 3.336-3.679-2.247.678-4.564 2.03-7.486 4.132-.513.366-1.942-2.968-1.906-5.533 3.371-2.49 8.5-4.066 12.64-4.066 5.79 0 9.123 3.224 9.123 7.694 0 3.737-3.114 7.84-7.657 7.987-2.362.061-3.876-1.265-4.65-2.902.092 2.532 1.409 5.65 4.943 5.65 3.853 0 5.704-2.326 8.463-5.795q.269-.339.55-.69c2.345-2.895 5.056-5.46 9.013-5.46 2.418 0 4.067 1.503 4.067 3.774 0 2.748-3.224 6.558-7.73 6.558-.77 0-1.472-.101-2.064-.301q-.024.173-.025.338c0 1.282.476 2.052 2.565 2.052 3.077 0 5.971-1.832 9.489-6.119 3.444-4.213 6.045-6.045 8.793-6.045 1.855 0 3.262 1.005 3.883 2.698C57.98 6.283 61.104 2.514 63.75 0c2.601 1.1 4.47 3.26 3.957 3.7-1.942 1.76-8.427 8.83-10.991 13.044-.66 1.099-1.283 2.308-1.283 2.894 0 .55.33.733.696.733 1.76 0 5.289-4.156 8.336-7.746 1.138-1.34 2.209-2.602 3.095-3.539 2.052.843 4.14 2.638 3.627 3.261-2.71 3.224-4.762 5.862-4.762 7.364 0 .403.146.66.696.66 1.026 0 1.978-.916 3.554-2.858.33-.403.732-.403.989.22.696 1.685 1.722 2.601 2.528 2.601.952 0 1.429-.843 1.429-2.125 0-.876-.107-1.895-.2-2.772-.069-.664-.13-1.246-.13-1.624m-59.096-.477c1.942 0 4.067-1.062 4.067-3.224 0-1.312-.814-2.521-2.99-2.89l-.342.535c-1.106 1.729-2.149 3.359-3.2 4.953.63.354 1.427.626 2.465.626m19.638.66c0-.587-.367-.99-.953-.99-1.47 0-3.687 2.063-4.73 4.055.385.15.837.232 1.323.232 2.601 0 4.36-1.978 4.36-3.297m9.636 5.312c0 .66.366 1.1 1.135 1.1 2.382 0 5.35-4.324 5.35-6.083 0-.732-.403-1.172-1.063-1.172-2.162 0-5.422 4.103-5.422 6.155M76.06 6.082c-.843 1.392-2.125 2.968-2.601 3.444-2.199-.916-4.25-2.748-3.957-3.26.806-1.43 2.125-2.968 2.601-3.445 2.198.916 4.25 2.785 3.957 3.261" clip-rule="evenodd"></path></svg> icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/redis.svg
defaultConfig: defaultConfig:
namespace: redis
image: redis:alpine image: redis:alpine
timezone: UTC timezone: UTC
host: redis.redis.svc.cluster.local
port: 6379 port: 6379
requiredSecrets: uri: redis://{{ .app.host }}:{{ .app.port }}/0
- apps.redis.password defaultSecrets:
- key: password

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: redis name: {{ .namespace }}

View File

@@ -7,7 +7,7 @@ metadata:
app: redis app: redis
spec: spec:
ports: ports:
- port: {{ .apps.redis.port }} - port: {{ .port }}
targetPort: {{ .apps.redis.port }} targetPort: {{ .port }}
selector: selector:
app: redis app: redis

View File

@@ -19,10 +19,10 @@ spec:
seccompProfile: seccompProfile:
type: RuntimeDefault type: RuntimeDefault
nodeSelector: nodeSelector:
nvidia.com/gpu.product: "{{ .apps.vllm.gpuProduct }}" nvidia.com/gpu.product: "{{ .gpuProduct }}"
containers: containers:
- name: vllm - name: vllm
image: "{{ .apps.vllm.image }}" image: "{{ .image }}"
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
@@ -31,10 +31,10 @@ spec:
- ALL - ALL
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
args: args:
- --model={{ .apps.vllm.model }} - --model={{ .model }}
- --max-model-len={{ .apps.vllm.maxModelLen }} - --max-model-len={{ .maxModelLen }}
- --tensor-parallel-size={{ .apps.vllm.tensorParallelSize }} - --tensor-parallel-size={{ .tensorParallelSize }}
- --gpu-memory-utilization={{ .apps.vllm.gpuMemoryUtilization }} - --gpu-memory-utilization={{ .gpuMemoryUtilization }}
{{- if .apps.vllm.enforceEager }} {{- if .apps.vllm.enforceEager }}
- --enforce-eager=True - --enforce-eager=True
{{- end }} {{- end }}
@@ -48,13 +48,13 @@ spec:
containerPort: 8000 containerPort: 8000
resources: resources:
requests: requests:
cpu: "{{ .apps.vllm.cpuRequest }}" cpu: "{{ .cpuRequest }}"
memory: "{{ .apps.vllm.memoryRequest }}" memory: "{{ .memoryRequest }}"
nvidia.com/gpu: {{ .apps.vllm.gpuCount }} nvidia.com/gpu: {{ .gpuCount }}
limits: limits:
cpu: "{{ .apps.vllm.cpuLimit }}" cpu: "{{ .cpuLimit }}"
memory: "{{ .apps.vllm.memoryLimit }}" memory: "{{ .memoryLimit }}"
nvidia.com/gpu: {{ .apps.vllm.gpuCount }} nvidia.com/gpu: {{ .gpuCount }}
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /v1/models path: /v1/models

View File

@@ -3,13 +3,13 @@ kind: Ingress
metadata: metadata:
name: vllm name: vllm
annotations: annotations:
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }} external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false" external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
traefik.ingress.kubernetes.io/router.tls: "true" traefik.ingress.kubernetes.io/router.tls: "true"
traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
spec: spec:
rules: rules:
- host: {{ .apps.vllm.domain }} - host: {{ .domain }}
http: http:
paths: paths:
- path: / - path: /
@@ -21,5 +21,5 @@ spec:
number: 8000 number: 8000
tls: tls:
- hosts: - hosts:
- {{ .apps.vllm.domain }} - {{ .domain }}
secretName: vllm-tls secretName: vllm-tls

Some files were not shown because too many files have changed in this diff Show More