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
icon: https://immich.app/assets/images/logo.png
requires:
- name: redis
- name: postgres
- name: pg
alias: db # Use a different reference name in templates
- name: redis # 'alias' and 'installedAs' default to 'name' value
defaultConfig:
serverImage: ghcr.io/immich-app/immich-server:release
mlImage: ghcr.io/immich-app/immich-machine-learning:release
@@ -36,13 +37,21 @@ defaultConfig:
mlPort: 3003
storage: 250Gi
cacheStorage: 10Gi
redisHostname: redis.redis.svc.cluster.local
dbHostname: postgres.postgres.svc.cluster.local
dbUsername: immich
redisHostname: "{{ .apps.redis.host }}" # Can reference 'requires' app configurations
dbHostname: "{{ .apps.pg.host }}"
db: # Configuration can be nested
name: immich
user: immich
host: "{{ .apps.pg.host }}"
port: "{{ .apps.pg.port }}"
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:
- apps.immich.dbPassword
- apps.postgres.password
- db.password # References postgres app via 'db' alias
- redis.auth # References redis app via 'redis' name (no alias)
```
#### Manifest Fields
@@ -53,11 +62,31 @@ requiredSecrets:
| `description` | Yes | Brief app description shown in listings |
| `version` | Yes | App version (follow upstream versioning) |
| `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` |
| `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`)
@@ -111,21 +140,7 @@ This means individual resources can use simple, component-specific selectors lik
### 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:
```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.
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:
### External DNS
@@ -133,12 +148,47 @@ Ingress resources should include external-dns annotations for automatic DNS mana
```yaml
annotations:
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }}
external-dns.alpha.kubernetes.io/target: {{ .domain }}
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`).
## 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 Initialization Jobs
@@ -177,7 +227,7 @@ When apps need database URLs with embedded credentials, **always use a dedicated
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
@@ -211,13 +261,16 @@ spec:
### 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:**
```yaml
defaultSecrets:
key: dbPassword # This app's database password
key: apiKey # This app's API key
requiredSecrets:
- apps.myapp.dbPassword
- apps.postgres.password
- db.password # Password from postgres dependency (aliased as 'db')
- redis.auth # Auth from redis dependency
```
**In resources:**
@@ -227,14 +280,26 @@ env:
valueFrom:
secretKeyRef:
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:**
1. List secrets in manifest's `requiredSecrets`
2. When adding an app, the system generates random values in the instance's `secrets.yaml`
3. When deploying, the system creates a Kubernetes Secret named `<app-name>-secrets`
4. Resources reference secrets using full dotted paths
1. Define app's own secrets in `defaultSecrets` (key, default mappings)
2. Reference dependency secrets in `requiredSecrets` (list)
3. When adding an app, the system:
- 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.
@@ -308,9 +373,11 @@ Before submitting a new or modified app, verify:
- [ ] **Manifest**
- [ ] `name` matches directory name
- [ ] All required fields present (`name`, `description`, `version`, `defaultConfig`)
- [ ] All template variables defined in `defaultConfig` or are standard Wild Cloud variables
- [ ] Secrets use dotted-path format (e.g., `apps.appname.secretname`)
- [ ] Dependencies listed in `requires` (if any)
- [ ] All template variables defined in `defaultConfig`
- [ ] `defaultSecrets` uses maps with 'key' and 'default' attributes
- [ ] `requiredSecrets` references use `<app-ref>.<key>` format
- [ ] Dependencies listed in `requires` with optional `alias` fields
- [ ] Manifest template references match dependency aliases or names
- [ ] **Kustomization**
- [ ] 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:`
- [ ] **Resources**
- [ ] All hardcoded values replaced with gomplate variables
- [ ] Secrets reference full dotted paths
- [ ] Security contexts on all pods (both pod-level and container-level)
- [ ] Simple component labels, no Helm-style labels
- [ ] 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
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.
## Repository Architecture
### App Structure
Each app follows a strict structure:
- **`manifest.yaml`** - App metadata, dependencies, default configuration, and secret requirements
- **`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
- When adding a new app to the directory, check to make sure you are adding the latest app version.
- Use traefik for ingress.
- Use postgres for database if supported.
- Keep config key naming (including nesting) consistent with other apps.
- 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
- when developing a new app:
- test with:
- reset to a fresh state between tests:
- secrets.yaml is not checked in and any values unrelated to your current task should be preserved

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

View File

@@ -1,5 +1,4 @@
---
# Source: discourse/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -18,219 +17,133 @@ spec:
component: web
spec:
automountServiceAccountToken: false
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
component: web
topologyKey: kubernetes.io/hostname
weight: 1
serviceAccountName: discourse
securityContext:
fsGroup: 0
fsGroupChangePolicy: Always
supplementalGroups: []
sysctls: []
initContainers:
containers:
- name: discourse
image: docker.io/bitnami/discourse:3.4.7-debian-12-r0
image: tiredofit/discourse:latest
imagePullPolicy: "IfNotPresent"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- CHOWN
- SYS_CHROOT
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
- DAC_OVERRIDE
drop:
- ALL
privileged: false
readOnlyRootFilesystem: false
runAsGroup: 0
runAsNonRoot: false
runAsUser: 0
seLinuxOptions: {}
seccompProfile:
type: RuntimeDefault
env:
- name: BITNAMI_DEBUG
value: "false"
- name: DISCOURSE_PASSWORD
# Admin configuration
- name: ADMIN_USER
value: {{ .adminUsername }}
- name: ADMIN_EMAIL
value: {{ .adminEmail }}
- name: ADMIN_PASS
valueFrom:
secretKeyRef:
name: discourse-secrets
key: apps.discourse.adminPassword
- name: DISCOURSE_PORT_NUMBER
value: "8080"
- name: DISCOURSE_EXTERNAL_HTTP_PORT_NUMBER
value: "80"
- name: DISCOURSE_DATABASE_PASSWORD
key: adminPassword
# Site configuration
- name: SITE_TITLE
value: {{ .siteName }}
- name: HOSTNAME
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:
secretKeyRef:
name: discourse-secrets
key: apps.discourse.dbPassword
- name: POSTGRESQL_CLIENT_CREATE_DATABASE_PASSWORD
key: dbPassword
# Redis configuration
- name: REDIS_HOST
value: {{ .redisHostname }}
- name: REDIS_PASS
valueFrom:
secretKeyRef:
name: discourse-secrets
key: apps.discourse.dbPassword
- name: DISCOURSE_REDIS_PASSWORD
key: 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:
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
key: smtpPassword
- name: SMTP_TLS
value: "{{ .smtp.tls }}"
# Container timezone
- name: TZ
value: {{ .timezone }}
ports:
- name: http
containerPort: 8080
containerPort: 3000
protocol: TCP
livenessProbe:
tcpSocket:
httpGet:
path: /
port: http
initialDelaySeconds: 500
periodSeconds: 10
timeoutSeconds: 5
initialDelaySeconds: 420
periodSeconds: 30
timeoutSeconds: 10
successThreshold: 1
failureThreshold: 6
readinessProbe:
httpGet:
path: /srv/status
path: /
port: http
initialDelaySeconds: 180
periodSeconds: 10
timeoutSeconds: 5
initialDelaySeconds: 360
periodSeconds: 30
timeoutSeconds: 10
successThreshold: 1
failureThreshold: 6
resources:
limits:
cpu: 1
ephemeral-storage: 2Gi
memory: 8Gi # for precompiling assets!
cpu: 2000m
ephemeral-storage: 10Gi
memory: 4Gi
requests:
cpu: 750m
cpu: 500m
ephemeral-storage: 50Mi
memory: 1Gi
volumeMounts:
- name: discourse-data
mountPath: /bitnami/discourse
subPath: discourse
- name: sidekiq
image: docker.io/bitnami/discourse:3.4.7-debian-12-r0
imagePullPolicy: "IfNotPresent"
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
- name: discourse-logs
mountPath: /data/logs
- name: discourse-uploads
mountPath: /data/uploads
- name: discourse-backups
mountPath: /data/backups
volumes:
- name: discourse-data
- name: discourse-logs
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
metadata:
name: discourse
namespace: "discourse"
namespace: "{{ .namespace }}"
annotations:
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
external-dns.alpha.kubernetes.io/target: "{{ .cloud.domain }}"
external-dns.alpha.kubernetes.io/target: "{{ .externalDnsDomain }}"
spec:
rules:
- host: "{{ .apps.discourse.domain }}"
- host: "{{ .domain }}"
http:
paths:
- path: /
@@ -22,5 +22,5 @@ spec:
name: http
tls:
- hosts:
- "{{ .apps.discourse.domain }}"
- "{{ .domain }}"
secretName: wildcard-external-wild-cloud-tls

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,39 @@
---
# Source: discourse/templates/pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: discourse
name: discourse-logs
namespace: discourse
spec:
accessModes:
- "ReadWriteOnce"
- ReadWriteOnce
resources:
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
metadata:
name: example-admin
namespace: example-admin
labels:
app: example-admin
spec:

View File

@@ -3,10 +3,9 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-admin
namespace: example-admin
spec:
rules:
- host: example-admin.{{ .cloud.internalDomain }}
- host: "{{ .host }}"
http:
paths:
- path: /
@@ -18,5 +17,5 @@ spec:
number: 80
tls:
- hosts:
- example-admin.{{ .cloud.internalDomain }}
- "{{ .host }}"
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.
version: 1.0.0
defaultConfig:
namespace: example-admin
externalDnsDomain: '{{ .cloud.domain }}'
host: '{{ .host }}'
tlsSecretName: wildcard-internal-wild-cloud-tls

View File

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

View File

@@ -4,7 +4,7 @@ kind: Ingress
metadata:
name: example-app
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
# Optional: Enable HTTPS redirection
@@ -15,7 +15,7 @@ metadata:
# traefik.ingress.kubernetes.io/auth-secret: basic-auth
spec:
rules:
- host: example-app.{{ .cloud.domain }}
- host: "{{ .host }}"
http:
paths:
- path: /
@@ -27,5 +27,5 @@ spec:
number: 80
tls:
- hosts:
- example-app.{{ .cloud.domain }}
- "{{ .host }}"
secretName: wildcard-wild-cloud-tls

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,12 +7,12 @@ metadata:
kubernetes.io/ingress.class: "traefik"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
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"
traefik.ingress.kubernetes.io/redirect-entry-point: https
spec:
rules:
- host: {{ .apps.ghost.domain }}
- host: {{ .domain }}
http:
paths:
- path: /
@@ -24,5 +24,5 @@ spec:
number: 80
tls:
- hosts:
- {{ .apps.ghost.domain }}
secretName: {{ .apps.ghost.tlsSecretName }}
- {{ .domain }}
secretName: {{ .tlsSecretName }}

View File

@@ -1,10 +1,13 @@
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
icon: https://ghost.org/images/logos/ghost-logo-orb.png
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/ghost.png
requires:
- name: mysql
defaultConfig:
namespace: ghost
externalDnsDomain: '{{ .cloud.domain }}'
image: docker.io/bitnami/ghost:5.118.1-debian-12-r0
domain: ghost.{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls
@@ -15,16 +18,17 @@ defaultConfig:
dbName: ghost
dbUser: ghost
adminUser: admin
adminEmail: "admin@{{ .cloud.domain }}"
blogTitle: "My Blog"
adminEmail: {{ .operator.email }}
blogTitle: My Blog
timezone: UTC
tlsSecretName: wildcard-wild-cloud-tls
smtp:
host: "{{ .cloud.smtp.host }}"
port: "{{ .cloud.smtp.port }}"
from: "{{ .cloud.smtp.from }}"
user: "{{ .cloud.smtp.user }}"
host: '{{ .cloud.smtp.host }}'
port: '{{ .cloud.smtp.port }}'
from: '{{ .cloud.smtp.from }}'
user: '{{ .cloud.smtp.user }}'
defaultSecrets:
- key: adminPassword
- key: dbPassword
- key: smtpPassword
requiredSecrets:
- apps.ghost.adminPassword
- apps.ghost.dbPassword
- apps.ghost.smtpPassword
- mysql.rootPassword

View File

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

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ Sensitive configuration is stored in the `gitea-secrets` secret and managed by t
- `dbPassword` - Database 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)
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
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
### Web UI Changes

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,12 @@
name: gitea
description: Gitea is a painless self-hosted Git service written in Go
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:
- name: postgres
defaultConfig:
namespace: gitea
externalDnsDomain: '{{ .cloud.domain }}'
image: gitea/gitea:1.24.3
appName: Gitea
domain: gitea.{{ .cloud.domain }}
@@ -16,18 +18,20 @@ defaultConfig:
dbUser: gitea
dbHost: postgres.postgres.svc.cluster.local
adminUser: admin
adminEmail: "admin@{{ .cloud.domain }}"
adminEmail: "{{ .operator.email }}"
dbPort: 5432
timezone: UTC
runMode: prod
smtp:
host: TBD
port: 465
from: no-reply@{{ .cloud.domain }}
user: TBD
host: '{{ .cloud.smtp.host }}'
port: '{{ .cloud.smtp.port }}'
user: '{{ .cloud.smtp.user }}'
from: '{{ .cloud.smtp.from }}'
defaultSecrets:
- key: adminPassword
- key: dbPassword
- key: secretKey
- key: jwtSecret
- key: smtpPassword
requiredSecrets:
- apps.gitea.adminPassword
- apps.gitea.dbPassword
- apps.gitea.secretKey
- apps.gitea.jwtSecret
- apps.gitea.smtpPassword
- postgres.password

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,12 +5,12 @@ metadata:
annotations:
traefik.ingress.kubernetes.io/router.tls: "true"
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"
traefik.ingress.kubernetes.io/router.middlewares: keila-cors@kubernetescrd
spec:
rules:
- host: {{ .apps.keila.domain }}
- host: {{ .domain }}
http:
paths:
- path: /
@@ -23,4 +23,4 @@ spec:
tls:
- secretName: "wildcard-wild-cloud-tls"
hosts:
- "{{ .apps.keila.domain }}"
- "{{ .domain }}"

View File

@@ -1,15 +1,18 @@
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.
version: 1.0.0
icon: https://www.keila.io/images/logo.svg
version: 0.17.1
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/keila.svg
requires:
- name: postgres
defaultConfig:
image: pentacent/keila:latest
namespace: keila
externalDnsDomain: "{{ .cloud.domain }}"
image: pentacent/keila:0.17.1
port: 4000
storage: 1Gi
domain: keila.{{ .cloud.domain }}
dbHostname: postgres.postgres.svc.cluster.local
dbHostname: "{{ .apps.postgres.host }}"
dbPort: "{{ .apps.postgres.port }}"
dbName: keila
dbUsername: keila
disableRegistration: "true"
@@ -20,12 +23,15 @@ defaultConfig:
port: "{{ .cloud.smtp.port }}"
from: "{{ .cloud.smtp.from }}"
user: "{{ .cloud.smtp.user }}"
tls: {{ .cloud.smtp.tls }}
startTls: {{ .cloud.smtp.startTls }}
tls: "{{ .cloud.smtp.tls }}"
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:
- apps.keila.secretKeyBase
- apps.keila.dbPassword
- apps.keila.dbUrl
- apps.keila.adminPassword
- apps.keila.smtpPassword
- apps.postgres.password
- postgres.password

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,16 +6,16 @@ metadata:
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
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"
spec:
ingressClassName: traefik
tls:
- hosts:
- {{ .apps.listmonk.domain }}
secretName: {{ .apps.listmonk.tlsSecretName }}
- {{ .domain }}
secretName: {{ .tlsSecretName }}
rules:
- host: {{ .apps.listmonk.domain }}
- host: {{ .domain }}
http:
paths:
- path: /

View File

@@ -1,11 +1,15 @@
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
icon: https://listmonk.app/static/images/logo.svg
requires:
- name: postgres
defaultConfig:
namespace: listmonk
externalDnsDomain: '{{ .cloud.domain }}'
domain: listmonk.{{ .cloud.domain }}
rootUrl: https://listmonk.{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls
storage: 1Gi
dbHost: postgres.postgres.svc.cluster.local
@@ -14,7 +18,9 @@ defaultConfig:
dbUser: listmonk
dbSSLMode: disable
timezone: UTC
defaultSecrets:
- key: dbPassword
- key: dbUrl
default: 'postgres://{{ .app.dbUser }}:{{ .secrets.dbPassword }}@{{ .app.dbHost }}:{{ .app.dbPort }}/{{ .app.dbName }}?sslmode={{ .app.dbSSLMode }}'
requiredSecrets:
- apps.listmonk.dbPassword
- apps.listmonk.dbUrl
- apps.postgres.password
- postgres.password

View File

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

View File

@@ -8,4 +8,4 @@ spec:
- ReadWriteOnce
resources:
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:
name: memcached
spec:
replicas: {{ .apps.memcached.replicas }}
replicas: {{ .replicas }}
selector:
matchLabels:
component: cache
@@ -14,24 +14,24 @@ spec:
spec:
containers:
- name: memcached
image: {{ .apps.memcached.image }}
image: "{{ .image }}"
ports:
- containerPort: {{ .apps.memcached.port }}
- containerPort: {{ .port }}
name: memcached
args:
- -m
- {{ .apps.memcached.memoryLimit }}
- "{{ .memoryLimit }}"
- -c
- "{{ .apps.memcached.maxConnections }}"
- "{{ .maxConnections }}"
- -p
- "{{ .apps.memcached.port }}"
- "{{ .port }}"
resources:
requests:
memory: {{ .apps.memcached.resources.requests.memory }}
cpu: {{ .apps.memcached.resources.requests.cpu }}
memory: "{{ .resources.requests.memory }}"
cpu: "{{ .resources.requests.cpu }}"
limits:
memory: {{ .apps.memcached.resources.limits.memory }}
cpu: {{ .apps.memcached.resources.limits.cpu }}
memory: "{{ .resources.limits.memory }}"
cpu: "{{ .resources.limits.cpu }}"
securityContext:
runAsNonRoot: true
runAsUser: 11211

View File

@@ -1,9 +1,11 @@
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
icon: https://memcached.org/memcached-logo.png
icon: https://www.vectorlogo.zone/logos/memcached/memcached-icon.svg
requires: []
defaultConfig:
namespace: memcached
image: memcached:1.6.32-alpine
port: 11211
memoryLimit: 64m
@@ -16,4 +18,4 @@ defaultConfig:
limits:
memory: 128Mi
cpu: 200m
requiredSecrets: []
defaultSecrets: []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,17 +1,18 @@
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
icon: https://docs.openwebui.com/assets/logo-dark.png
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/open-webui.svg
requires: []
defaultConfig:
namespace: open-webui
externalDnsDomain: '{{ .cloud.domain }}'
image: ghcr.io/open-webui/open-webui:main
port: 8080
storage: 10Gi
domain: chat.{{ .cloud.domain }}
# vLLM integration - connect to existing vLLM service
vllmApiUrl: http://vllm-service.llm.svc.cluster.local:8000/v1
# Authentication settings
enableAuth: true
enableSignup: false
requiredSecrets:
- apps.openWebui.secretKey
defaultSecrets: []

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,14 @@
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
icon: https://www.openproject.org/assets/images/openproject-logo.png
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/openproject.svg
requires:
- name: postgres
- name: memcached
defaultConfig:
namespace: openproject
externalDnsDomain: '{{ .cloud.domain }}'
serverImage: openproject/openproject:16.1.1-slim
timezone: UTC
serverPort: 8080
@@ -20,14 +23,15 @@ defaultConfig:
hsts: true
seedLocale: en
adminUserName: OpenProject Admin
adminUserEmail: "{{ .operator.email }}"
adminUserEmail: '{{ .operator.email }}'
adminPasswordReset: true
postgresStatementTimeout: 120s
tmpVolumesStorage: 2Gi
tlsSecretName: wildcard-wild-cloud-tls
cacheStore: memcache
railsRelativeUrlRoot: ""
railsRelativeUrlRoot: ''
defaultSecrets:
- key: dbPassword
- key: adminPassword
requiredSecrets:
- apps.openproject.dbPassword
- apps.openproject.adminPassword
- apps.postgres.password
- postgres.password

View File

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

View File

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

View File

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

View File

@@ -43,7 +43,7 @@ spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: {{ .apps.openproject.tmpVolumesStorage }}
storage: {{ .tmpVolumesStorage }}
- name: app-tmp
# we can't use emptyDir due to the sticky bit / world writable issue
# see: https://github.com/kubernetes/kubernetes/issues/110835
@@ -55,7 +55,7 @@ spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: {{ .apps.openproject.tmpVolumesStorage }}
storage: {{ .tmpVolumesStorage }}
- name: "data"
persistentVolumeClaim:
claimName: openproject
@@ -72,8 +72,13 @@ spec:
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
image: {{ .apps.openproject.serverImage }}
image: postgres:17
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:
- configMapRef:
name: openproject-core
@@ -84,14 +89,12 @@ spec:
valueFrom:
secretKeyRef:
name: openproject-secrets
key: apps.openproject.dbPassword
key: dbPassword
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
valueFrom:
secretKeyRef:
name: openproject-secrets
key: apps.openproject.adminPassword
args:
- /app/docker/prod/wait-for-db
key: adminPassword
resources:
limits:
memory: 1Gi
@@ -115,7 +118,7 @@ spec:
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
image: {{ .apps.openproject.serverImage }}
image: {{ .serverImage }}
imagePullPolicy: Always
envFrom:
- configMapRef:
@@ -127,12 +130,12 @@ spec:
valueFrom:
secretKeyRef:
name: openproject-secrets
key: apps.openproject.dbPassword
key: dbPassword
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
valueFrom:
secretKeyRef:
name: openproject-secrets
key: apps.openproject.adminPassword
key: adminPassword
args:
- /app/docker/prod/web
volumeMounts:

View File

@@ -43,7 +43,7 @@ spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: {{ .apps.openproject.tmpVolumesStorage }}
storage: {{ .tmpVolumesStorage }}
- name: app-tmp
# we can't use emptyDir due to the sticky bit / world writable issue
# see: https://github.com/kubernetes/kubernetes/issues/110835
@@ -55,7 +55,7 @@ spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: {{ .apps.openproject.tmpVolumesStorage }}
storage: {{ .tmpVolumesStorage }}
- name: "data"
persistentVolumeClaim:
claimName: openproject
@@ -72,8 +72,13 @@ spec:
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
image: {{ .apps.openproject.serverImage }}
image: postgres:17
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:
- configMapRef:
name: openproject-core
@@ -84,15 +89,12 @@ spec:
valueFrom:
secretKeyRef:
name: openproject-secrets
key: apps.openproject.dbPassword
key: dbPassword
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
valueFrom:
secretKeyRef:
name: openproject-secrets
key: apps.openproject.adminPassword
args:
- bash
- /app/docker/prod/wait-for-db
key: adminPassword
resources:
limits:
memory: 1Gi
@@ -116,7 +118,7 @@ spec:
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
image: {{ .apps.openproject.serverImage }}
image: {{ .serverImage }}
imagePullPolicy: Always
envFrom:
- configMapRef:
@@ -132,7 +134,7 @@ spec:
valueFrom:
secretKeyRef:
name: openproject-secrets
key: apps.openproject.dbPassword
key: dbPassword
- name: "OPENPROJECT_GOOD_JOB_QUEUES"
value: ""
volumeMounts:

View File

@@ -15,7 +15,7 @@ spec:
spec:
containers:
- name: postgres
image: "{{ .apps.postgres.image }}"
image: "{{ .image }}"
args:
[
"-c",
@@ -35,16 +35,16 @@ spec:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
- name: TZ
value: "{{ .apps.postgres.timezone }}"
value: "{{ .timezone }}"
- name: POSTGRES_DB
value: "{{ .apps.postgres.database }}"
value: "{{ .database }}"
- name: POSTGRES_USER
value: "{{ .apps.postgres.user }}"
value: "{{ .user }}"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secrets
key: apps.postgres.password
key: password
volumeMounts:
- name: postgres-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
icon: https://www.postgresql.org/media/img/about/press/elephant.png
defaultConfig:
namespace: postgres
host: postgres.postgres.svc.cluster.local
port: 5432
database: postgres
user: postgres
storage: 10Gi
image: pgvector/pgvector:pg15
timezone: UTC
requiredSecrets:
- apps.postgres.password
defaultSecrets:
- key: password

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,10 +2,13 @@ name: redis
install: true
description: Redis is an open source, in-memory data structure store, used as a database, cache and message broker.
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:
namespace: redis
image: redis:alpine
timezone: UTC
host: redis.redis.svc.cluster.local
port: 6379
requiredSecrets:
- apps.redis.password
uri: redis://{{ .app.host }}:{{ .app.port }}/0
defaultSecrets:
- key: password

View File

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

View File

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

View File

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

View File

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

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