diff --git a/apps/charts/immich/templates/database.yaml b/apps/charts/immich/templates/database.yaml index 5628c63..4a67382 100644 --- a/apps/charts/immich/templates/database.yaml +++ b/apps/charts/immich/templates/database.yaml @@ -19,7 +19,7 @@ metadata: spec: strategy: type: Recreate - replicas: 1 + replicas: 0 revisionHistoryLimit: 0 selector: matchLabels: diff --git a/apps/charts/immich/templates/deployment.yaml b/apps/charts/immich/templates/deployment.yaml index 9d88516..d370322 100644 --- a/apps/charts/immich/templates/deployment.yaml +++ b/apps/charts/immich/templates/deployment.yaml @@ -5,7 +5,7 @@ metadata: spec: strategy: type: Recreate - replicas: 1 + replicas: 0 revisionHistoryLimit: 0 selector: matchLabels: diff --git a/apps/common/README.md b/apps/common/README.md index 83d5338..da34f47 100644 --- a/apps/common/README.md +++ b/apps/common/README.md @@ -1,54 +1,601 @@ # Common Library Chart -This is a Helm library chart that provides shared template helpers for all application charts in this repository. +This is a Helm library chart that provides shared template helpers for all application charts in this repository. It dramatically reduces code duplication by providing standardized templates for common Kubernetes resources. ## Quick Start -To use this library chart in your application chart, add it as a dependency in your `Chart.yaml`: +### 1. Create Your Chart Structure + +```bash +mkdir -p apps/charts/my-app/templates +``` + +### 2. Add Common Library Dependency + +Create `Chart.yaml`: ```yaml apiVersion: v2 version: 1.0.0 -name: your-app +name: my-app dependencies: - name: common version: 1.0.0 repository: file://../../common ``` -Then run `helm dependency build` to download the dependency. +Run `helm dependency build` to download the dependency. -## Documentation +### 3. Create Standardized values.yaml -- **[MIGRATION.md](./MIGRATION.md)** - Complete guide for migrating existing charts -- **[TEMPLATING.md](./TEMPLATING.md)** - Guide to using placeholders in values.yaml +Create `values.yaml` with the standardized structure (see [Values Structure](#values-structure) below). + +### 4. Create Template Files + +Replace complex templates with simple includes: + +```yaml +# templates/deployment.yaml +{{ include "common.deployment" . }} + +# templates/service.yaml +{{ include "common.service" . }} + +# templates/pvc.yaml +{{ include "common.pvc" . }} + +# templates/virtual-service.yaml +{{ include "common.virtualService" . }} +``` + +## Values Structure + +The library expects a standardized `values.yaml` structure. Here's a complete example: + +```yaml +# Image configuration +image: + repository: docker.io/org/my-app + tag: v1.0.0 + pullPolicy: IfNotPresent + +# Subdomain for ingress +subdomain: my-app + +# Deployment configuration +deployment: + strategy: Recreate # or RollingUpdate + replicas: 1 + revisionHistoryLimit: 0 # Optional, defaults to 2. Set to 0 to disable history + hostNetwork: false # Optional, for host networking + dnsPolicy: ClusterFirst # Optional, for custom DNS policy + name: "" # Optional suffix for deployment name (e.g., "main" results in "{release}-main") + +# Container configuration +container: + # Single port (simple case) + port: 8080 + + # OR multiple ports (use array) + ports: + - name: http + port: 8080 + protocol: TCP + - name: grpc + port: 9090 + protocol: TCP + + # Health probe configuration + healthProbe: + type: httpGet # or tcpSocket + port: http # Use named port or number + path: /health # Required for httpGet + # Optional: initialDelaySeconds, periodSeconds, etc. + + # Resource limits (optional) + resources: + limits: + cpu: "1000m" + memory: "512Mi" + requests: + cpu: "500m" + memory: "256Mi" + + # Security context (optional) + securityContext: + privileged: false + runAsUser: 1000 + runAsGroup: 1000 + +# Service configuration +service: + # Single service (simple case) + port: 80 + targetPort: 8080 # Optional, defaults to container port + type: ClusterIP + + # OR multiple services (use array) + ports: + - name: http + port: 80 + targetPort: 8080 + protocol: TCP + type: ClusterIP + - name: grpc + port: 9090 + targetPort: 9090 + protocol: TCP + type: ClusterIP + serviceName: grpc # Creates separate service: {release}-grpc + +# Volume configuration +volumes: + - name: data + mountPath: /data + persistentVolumeClaim: data # References PVC from persistentVolumeClaims + - name: config + mountPath: /config + emptyDir: {} # For temporary/ephemeral storage + - name: external + mountPath: /external + persistentVolumeClaim: external-pvc # External PVC (not prefixed with release name) + +# Persistent volume claims +persistentVolumeClaims: + - name: data + size: 10Gi + - name: cache + size: 5Gi + +# VirtualService configuration (Istio) +virtualService: + enabled: true + gateways: + public: true # Enable public gateway + private: true # Enable private gateway + servicePort: 80 # Optional, defaults to service port + +# OIDC client configuration (optional) +oidc: + enabled: true + redirectUris: + - "/api/auth/callback/authentik" + - "/oauth/oidc/callback" + subjectMode: user_username # Optional: user_username (default), user_email, user_id + +# Database configuration (optional) +database: + enabled: true + +# External secrets configuration (optional) +externalSecrets: + - name: "{release}-secrets" # Use {release} placeholder + passwords: + - name: apiKey + length: 32 + encoding: hex # or base64, alphanumeric + allowRepeat: true + - name: encryptionKey + length: 64 + encoding: base64 + allowRepeat: false + +# Environment variables +env: + # Simple value + APP_NAME: "My App" + + # Using placeholders + BASE_URL: + value: "https://{subdomain}.{domain}" + TZ: + value: "{timezone}" + + # Secret references + DATABASE_URL: + valueFrom: + secretKeyRef: + name: "{release}-connection" + key: url + OAUTH_CLIENT_ID: + valueFrom: + secretKeyRef: + name: "{release}-oidc-credentials" + key: clientId + API_KEY: + valueFrom: + secretKeyRef: + name: "{release}-secrets" + key: apiKey +``` + +## Template Files + +### Basic Application + +For a simple application with persistent storage: + +```yaml +# templates/deployment.yaml +{{ include "common.deployment" . }} + +# templates/service.yaml +{{ include "common.service" . }} + +# templates/pvc.yaml +{{ include "common.pvc" . }} + +# templates/virtual-service.yaml +{{ include "common.virtualService" . }} +``` + +### With OIDC Authentication + +Add OIDC client template: + +```yaml +# templates/client.yaml (or oidc.yaml) +{{ include "common.oidc" . }} +``` + +### With Database + +Add database template: + +```yaml +# templates/database.yaml +{{ include "common.database" . }} +``` + +### With External Secrets + +Add secret generation templates: + +```yaml +# templates/secret-password-generators.yaml +{{ include "common.externalSecrets.passwordGenerators" . }} + +# templates/secret-external-secrets.yaml +{{ include "common.externalSecrets.externalSecrets" . }} +``` + +## Complete Examples + +### Example 1: Simple Stateless Application + +```yaml +# values.yaml +image: + repository: docker.io/org/my-app + tag: v1.0.0 + pullPolicy: IfNotPresent + +subdomain: my-app + +deployment: + strategy: RollingUpdate + replicas: 2 + +container: + port: 8080 + healthProbe: + type: httpGet + path: /health + port: 8080 + +service: + port: 80 + type: ClusterIP + +virtualService: + enabled: true + gateways: + public: true + +env: + TZ: + value: "{timezone}" +``` + +```yaml +# templates/deployment.yaml +{{ include "common.deployment" . }} + +# templates/service.yaml +{{ include "common.service" . }} + +# templates/virtual-service.yaml +{{ include "common.virtualService" . }} +``` + +### Example 2: Application with OIDC and Database + +```yaml +# values.yaml +image: + repository: docker.io/org/my-app + tag: v1.0.0 + pullPolicy: IfNotPresent + +subdomain: my-app + +deployment: + strategy: Recreate + replicas: 1 + +container: + port: 8080 + healthProbe: + type: tcpSocket + port: 8080 + +service: + port: 80 + type: ClusterIP + +volumes: + - name: data + mountPath: /data + persistentVolumeClaim: data + +persistentVolumeClaims: + - name: data + size: 10Gi + +virtualService: + enabled: true + gateways: + public: true + private: true + +oidc: + enabled: true + redirectUris: + - "/api/auth/callback/authentik" + subjectMode: user_username + +database: + enabled: true + +env: + TZ: + value: "{timezone}" + BASE_URL: + value: "https://{subdomain}.{domain}" + OAUTH_CLIENT_ID: + valueFrom: + secretKeyRef: + name: "{release}-oidc-credentials" + key: clientId + OAUTH_CLIENT_SECRET: + valueFrom: + secretKeyRef: + name: "{release}-oidc-credentials" + key: clientSecret + OAUTH_ISSUER_URL: + valueFrom: + secretKeyRef: + name: "{release}-oidc-credentials" + key: issuer + DATABASE_URL: + valueFrom: + secretKeyRef: + name: "{release}-connection" + key: url +``` + +```yaml +# templates/deployment.yaml +{{ include "common.deployment" . }} + +# templates/service.yaml +{{ include "common.service" . }} + +# templates/pvc.yaml +{{ include "common.pvc" . }} + +# templates/virtual-service.yaml +{{ include "common.virtualService" . }} + +# templates/client.yaml +{{ include "common.oidc" . }} + +# templates/database.yaml +{{ include "common.database" . }} +``` + +### Example 3: Application with Generated Secrets + +```yaml +# values.yaml +# ... other configuration ... + +externalSecrets: + - name: "{release}-secrets" + passwords: + - name: encryptionKey + length: 64 + encoding: base64 + allowRepeat: true + - name: apiToken + length: 32 + encoding: hex + allowRepeat: false + +env: + ENCRYPTION_KEY: + valueFrom: + secretKeyRef: + name: "{release}-secrets" + key: encryptionKey + API_TOKEN: + valueFrom: + secretKeyRef: + name: "{release}-secrets" + key: apiToken +``` + +```yaml +# templates/secret-password-generators.yaml +{{ include "common.externalSecrets.passwordGenerators" . }} + +# templates/secret-external-secrets.yaml +{{ include "common.externalSecrets.externalSecrets" . }} +``` ## Available Templates The library provides full resource templates that can be included directly: -- `common.deployment` - Full Deployment resource +- `common.deployment` - Full Deployment resource with all standard configurations - `common.service` - Full Service resource(s) - supports multiple services - `common.pvc` - Full PVC resources - supports multiple PVCs -- `common.virtualService` - Full VirtualService resources (public + private) -- `common.dns` - Full DNSRecord resource -- `common.oidc` - Full AuthentikClient resource -- `common.database` - Full PostgresDatabase resource -- `common.externalSecrets` - Full ExternalSecret resources with Password generators +- `common.virtualService` - Full VirtualService resources (public + private gateways) +- `common.oidc` - Full AuthentikClient resource for OIDC authentication +- `common.database` - Full PostgresDatabase resource for database provisioning +- `common.externalSecrets.passwordGenerators` - Password generator resources +- `common.externalSecrets.externalSecrets` - ExternalSecret resources -## Usage Example +## Secret References -Replace your template files with simple includes: +### OIDC Credentials + +When `oidc.enabled: true`, the AuthentikClient creates a secret named `{release}-oidc-credentials` with: +- `clientId` - OAuth client ID +- `clientSecret` - OAuth client secret +- `issuer` - OIDC provider issuer URL + +### Database Connection + +When `database.enabled: true`, the PostgresDatabase creates a secret named `{release}-connection` with: +- `url` - Complete PostgreSQL connection URL +- `host` - Database hostname +- `port` - Database port +- `database` - Database name +- `user` - Database username +- `password` - Database password + +### External Secrets + +External secrets are created with the name specified in `externalSecrets[].name` (use `{release}` placeholder). Each password field becomes a key in the secret. + +## Placeholders + +Use placeholders in `values.yaml` for dynamic values that are resolved at template time: + +- `{release}` - Release name (e.g., "my-app") +- `{namespace}` - Release namespace (e.g., "default") +- `{fullname}` - Full app name (same as release name) +- `{subdomain}` - Application subdomain (from `subdomain` value) +- `{domain}` - Global domain (from `globals.domain`) +- `{timezone}` - Global timezone (from `globals.timezone`) + +**Example:** ```yaml -# deployment.yaml -{{ include "common.deployment" . }} +env: + BASE_URL: + value: "https://{subdomain}.{domain}" + SECRET_NAME: + value: "{release}-secrets" +``` -# service.yaml -{{ include "common.service" . }} +## Advanced Configuration -# pvc.yaml -{{ include "common.pvc" . }} +### Multiple Services + +Define multiple services using the `ports` array: + +```yaml +service: + ports: + - name: http + port: 80 + targetPort: 8080 + type: ClusterIP + - name: grpc + port: 9090 + targetPort: 9090 + type: ClusterIP + serviceName: grpc # Creates separate service: {release}-grpc +``` + +### Multiple Ports + +Define multiple container ports: + +```yaml +container: + ports: + - name: http + port: 8080 + protocol: TCP + - name: metrics + port: 9090 + protocol: TCP +``` + +### Custom Deployment Name + +Use a suffix for the deployment name: + +```yaml +deployment: + name: "main" # Results in deployment name: {release}-main +``` + +This also affects service selectors and virtual service destinations. + +### Host Networking + +Enable host networking: + +```yaml +deployment: + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet +``` + +### Privileged Containers + +Run containers in privileged mode: + +```yaml +container: + securityContext: + privileged: true +``` + +### Resource Limits + +Set CPU and memory limits: + +```yaml +container: + resources: + limits: + cpu: "2000m" + memory: "2Gi" + requests: + cpu: "1000m" + memory: "1Gi" +``` + +## Custom Templates + +For application-specific resources that aren't covered by the common library, create custom templates. You can still use helper functions: + +```yaml +# templates/custom-resource.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "common.fullname" . }}-config + labels: + {{- include "common.labels" . | nindent 4 }} +data: + config.yaml: | + # Custom configuration ``` ## Available Helpers @@ -71,52 +618,33 @@ Helper functions for custom templates: - `common.virtualServiceGatewaysPublic` - Public gateway list - `common.virtualServiceGatewaysPrivate` - Private gateway list -## Values Structure +## Best Practices -The library expects a standardized values structure. See migrated charts (`audiobookshelf`, `forgejo`, `baikal`, `blinko`) for examples. +1. **Always use placeholders** for dynamic values (`{release}`, `{subdomain}`, `{domain}`, `{timezone}`) +2. **Use named ports** when defining multiple ports (e.g., `port: http` instead of `port: 8080`) +3. **Set `revisionHistoryLimit: 0`** for stateful applications using Recreate strategy +4. **Reference secrets** using the `{release}` placeholder in secret names +5. **Use the standardized structure** - it makes charts consistent and easier to maintain +6. **Keep custom templates minimal** - only for truly application-specific resources -### OIDC Configuration +## Testing -To enable OIDC authentication, configure the `oidc` section in your `values.yaml`: +After creating your chart: -```yaml -oidc: - enabled: true - redirectUris: - - "/api/auth/callback/authentik" - subjectMode: user_username # Optional, defaults to "user_username" -``` +1. Build dependencies: `helm dependency build` +2. Lint: `helm lint .` +3. Template: `helm template my-app . --set globals.environment=prod --set globals.domain=example.com ...` +4. Dry-run: `helm install my-app . --dry-run --debug` -**Subject Mode Options:** -- `user_username` (default) - Uses the username as the subject identifier -- `user_email` - Uses the email address as the subject identifier -- `user_id` - Uses the user ID as the subject identifier +## Documentation -The AuthentikClient resource creates a secret named `{release}-oidc-credentials` containing: -- `clientId` - OAuth client ID -- `clientSecret` - OAuth client secret -- `issuer` - OIDC provider issuer URL +- **[MIGRATION.md](./MIGRATION.md)** - Complete guide for migrating existing charts +- **[TEMPLATING.md](./TEMPLATING.md)** - Detailed guide to using placeholders in values.yaml -Reference these in your environment variables using placeholders: +## Examples -```yaml -env: - OAUTH2_CLIENT_ID: - valueFrom: - secretKeyRef: - name: "{release}-oidc-credentials" - key: clientId -``` - -## Placeholders - -Use placeholders in `values.yaml` for dynamic values: - -- `{release}` - Release name -- `{namespace}` - Release namespace -- `{fullname}` - Full app name -- `{subdomain}` - Application subdomain -- `{domain}` - Global domain -- `{timezone}` - Global timezone - -See [TEMPLATING.md](./TEMPLATING.md) for complete documentation. +See migrated charts for real-world examples: +- `apps/charts/readeck` - Simple application +- `apps/charts/miniflux` - Application with OIDC and database +- `apps/charts/n8n` - Complex application with multiple services +- `apps/charts/home-assistant` - Application with host networking and privileged containers