356 lines
12 KiB
Markdown
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.
|