Files
wild-directory/ADDING-APPS.md

356 lines
12 KiB
Markdown

# Adding Wild Cloud Apps
This guide is for contributors and maintainers who want to create or modify Wild Cloud apps. If you're looking to use existing apps, see [README.md](README.md).
## Overview
Wild Cloud apps are Kubernetes applications packaged as Kustomize configurations with standardized conventions for configuration management, secrets handling, and deployment.
## Required Files
Each app directory must contain:
1. **`manifest.yaml`** - App metadata and configuration schema
2. **`kustomization.yaml`** - Kustomize configuration with Wild Cloud labels
3. **Resource files** - Kubernetes manifests (deployments, services, ingresses, etc.)
### App Manifest (`manifest.yaml`)
The manifest defines the app's metadata, dependencies, configuration schema, and secret requirements.
This is the contents of an example `manifest.yaml` file for an app named "immich":
```yaml
name: immich
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
requires:
- name: redis
- name: postgres
defaultConfig:
serverImage: ghcr.io/immich-app/immich-server:release
mlImage: ghcr.io/immich-app/immich-machine-learning:release
timezone: UTC
serverPort: 2283
mlPort: 3003
storage: 250Gi
cacheStorage: 10Gi
redisHostname: redis.redis.svc.cluster.local
dbHostname: postgres.postgres.svc.cluster.local
dbUsername: immich
domain: immich.{{ .cloud.domain }}
requiredSecrets:
- apps.immich.dbPassword
- apps.postgres.password
```
#### Manifest Fields
| Field | Required | Description |
|-------|----------|-------------|
| `name` | Yes | App identifier (must match directory name) |
| `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`) |
| `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`) |
**Important:** All configuration keys referenced in templates (via `{{ .apps.appname.key }}`) must be defined in `defaultConfig` or be standard Wild Cloud variables.
### Kustomization (`kustomization.yaml`)
The kustomization file defines how Kubernetes resources are built and applies Wild Cloud's standard labels.
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: immich
labels:
- includeSelectors: true
pairs:
app: immich
managedBy: kustomize
partOf: wild-cloud
resources:
- deployment-server.yaml
- deployment-machine-learning.yaml
- deployment-microservices.yaml
- ingress.yaml
- namespace.yaml
- pvc.yaml
- service.yaml
- db-init-job.yaml
```
#### Kustomization Requirements
- **Namespace**: Must match the app name
- **Labels**: Must include standard Wild Cloud labels with `includeSelectors: true`
- **Resources**: List all Kubernetes manifest files
#### Labeling Strategy
Wild Cloud uses Kustomize's `includeSelectors: true` feature to automatically apply standard labels to all resources AND their selectors:
```yaml
labels:
- includeSelectors: true
pairs:
app: myapp # App name (matches directory)
managedBy: kustomize
partOf: wild-cloud
```
This means individual resources can use simple, component-specific selectors like `component: web`, and Kustomize will automatically expand them to include all Wild Cloud labels.
**Do NOT use Helm-style labels** (`app.kubernetes.io/name`, `app.kubernetes.io/instance`). Use simple component labels (`component: web`, `component: worker`, etc.) instead.
## Configuration Templates
### 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.
### External DNS
Ingress resources should include external-dns annotations for automatic DNS management:
```yaml
annotations:
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }}
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
```
This creates a CNAME from the app subdomain to the cluster domain (e.g., `myapp.cloud.example.com``cloud.example.com`).
## Database Patterns
### Database Initialization Jobs
Apps requiring PostgreSQL or MySQL should include a database initialization job (`db-init-job.yaml`):
**Purpose:**
- Creates the application database (if it doesn't exist)
- Creates/updates the application user with proper credentials
- Grants necessary permissions
- Installs required database extensions (e.g., PostgreSQL's `vector`, `cube`, `earthdistance`)
**Implementation requirements:**
- Use `restartPolicy: OnFailure`
- Include in `kustomization.yaml` resources
- Use appropriate security context (e.g., `runAsUser: 999` for PostgreSQL)
**Example apps:** `immich`, `gitea`, `openproject`, `discourse`
### Database URL Configuration
When apps need database URLs with embedded credentials, **always use a dedicated `dbUrl` secret**.
**Wrong** - Kustomize cannot process runtime env var substitution:
```yaml
- name: DB_URL
value: "postgresql://user:$(DB_PASSWORD)@host/db" # This won't work!
```
**Correct** - Use a dedicated secret:
```yaml
- name: DB_URL
valueFrom:
secretKeyRef:
name: myapp-secrets
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.
## Security Requirements
### Security Contexts
**All pods must comply with Pod Security Standards.** Include security contexts at both pod and container levels:
```yaml
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 999 # Use appropriate non-root UID
runAsGroup: 999 # Use appropriate GID
seccompProfile:
type: RuntimeDefault
containers:
- name: container-name
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false # Set to true when possible
```
**Common user IDs:**
- PostgreSQL: `runAsUser: 999`
- Redis: `runAsUser: 999`
- MySQL: Consult the container image documentation
### Secrets Management
Secrets use a **full dotted-path naming convention** to prevent naming conflicts:
**In manifest:**
```yaml
requiredSecrets:
- apps.myapp.dbPassword
- apps.postgres.password
```
**In resources:**
```yaml
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-secrets
key: apps.myapp.dbPassword # Full dotted path, not just "dbPassword"
```
**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
**Important:** Never commit `secrets.yaml` to Git. Templates should only reference secrets, never contain actual secret values.
## Converting from Helm Charts
Wild Cloud prefers Kustomize over Helm for simplicity and Git-friendliness. When an official Helm chart exists, convert it rather than creating manifests from scratch.
### Conversion Process
1. **Extract and render the Helm chart:**
```bash
helm fetch --untar --untardir charts repo/chart-name
helm template --output-dir base --namespace myapp --values values.yaml myapp charts/chart-name
cd base/chart-name
```
2. **Add namespace manifest:**
```bash
cat <<EOF > namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: myapp
EOF
```
3. **Create kustomization:**
```bash
kustomize create --autodetect
```
4. **Convert to Wild Cloud format:**
- Create `manifest.yaml` with app metadata
- Replace hardcoded values with gomplate variables (e.g., `{{ .cloud.domain }}`)
- Update secrets to use dotted-path convention
- Replace Helm labels with Wild Cloud standard labels
- Add `includeSelectors: true` to kustomization
- Use simple component labels (`component: web`, not `app.kubernetes.io/name`)
- Add security contexts to all pods
- Add external-dns annotations to ingresses
### Example Label Migration
**Helm style:**
```yaml
labels:
app.kubernetes.io/name: myapp
app.kubernetes.io/instance: release-name
app.kubernetes.io/component: server
```
**Wild Cloud style:**
```yaml
# In kustomization.yaml (applied automatically)
labels:
- includeSelectors: true
pairs:
app: myapp
managedBy: kustomize
partOf: wild-cloud
# In individual resources
labels:
component: server # Simple component label
```
## Validation Checklist
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)
- [ ] **Kustomization**
- [ ] Includes standard Wild Cloud labels with `includeSelectors: true`
- [ ] Namespace matches app name
- [ ] 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
- [ ] Database apps include init jobs (if applicable)
- [ ] **Testing**
- [ ] Templates compile successfully with sample config
- [ ] App deploys without errors in test cluster
- [ ] All dependencies work correctly
## Contributing
Contributions are welcome! To contribute:
1. Fork the repository
2. Create a new app directory following the structure above
3. Test your app thoroughly
4. Submit a pull request with:
- Description of the app and its purpose
- Any special configuration notes
- Dependencies required
## Notice: Third-Party Software
The Kubernetes manifests and Kustomize files in this directory are designed to deploy **third-party software**.
Unless otherwise stated, the software deployed by these manifests **is not authored or maintained** by this project. All copyrights, licenses, and responsibilities for that software remain with the respective upstream authors.
These files are provided solely for convenience and automation. Users are responsible for reviewing and complying with the licenses of the software they deploy.
This project is licensed under the GNU AGPLv3 or later, but this license does **not apply** to the third-party software being deployed.
See individual deployment directories for upstream project links and container sources.