13 KiB
Application Helm Charts Guide
This document provides guidelines for creating and maintaining Helm charts in this homelab project.
Project Structure
apps/
├── charts/ # Individual application Helm charts
│ ├── app-name/
│ │ ├── Chart.yaml
│ │ ├── values.yaml
│ │ └── templates/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ ├── pvc.yaml
│ │ ├── client.yaml # OIDC client configuration
│ │ ├── database.yaml # Database provisioning
│ │ ├── secret.yaml # Secret generation
│ │ └── external-http-service.yaml
│ └── ...
└── root/ # ArgoCD ApplicationSet for auto-discovery
├── Chart.yaml
├── values.yaml
└── templates/
├── applicationset.yaml
└── project.yaml
foundation/
├── charts/ # Foundation service Helm charts
│ └── ...
└── root/ # ArgoCD ApplicationSet for foundation services
shared/
├── charts/ # Shared service Helm charts
│ └── ...
└── root/ # ArgoCD ApplicationSet for shared services
ArgoCD ApplicationSets
This project uses three separate ArgoCD ApplicationSets to manage different categories of services:
- apps/ - Individual applications (web apps, tools, services)
- foundation/ - Core infrastructure for the cluster (monitoring, certificates, operators)
- shared/ - Infrastructure shared between applications (databases, message queues, caches)
Each category has its own root/ chart containing an ApplicationSet that auto-discovers and deploys charts from its respective charts/ directory.
Creating a New Application Chart
1. Basic Chart Structure
Create a new directory under apps/charts/ with the following structure:
mkdir -p apps/charts/my-app/templates
Chart.yaml
apiVersion: v2
version: 1.0.0
name: my-app
values.yaml
image:
repository: docker.io/org/my-app
tag: v1.0.0
pullPolicy: IfNotPresent
subdomain: my-app
2. Core Templates
Deployment Template
Create templates/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}"
spec:
strategy:
type: Recreate
replicas: 1
revisionHistoryLimit: 0
selector:
matchLabels:
app: "{{ .Release.Name }}"
template:
metadata:
labels:
app: "{{ .Release.Name }}"
spec:
containers:
- name: "{{ .Release.Name }}"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
tcpSocket:
port: http
readinessProbe:
tcpSocket:
port: http
volumeMounts:
- mountPath: /data
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-data"
Service Template
Create templates/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: "{{ .Release.Name }}"
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: "{{ .Release.Name }}"
Persistent Volume Claim
Create templates/pvc.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: "{{ .Release.Name }}-data"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
Custom Resource Definitions (CRDs)
This project uses several custom resources that are managed by operators in the cluster:
1. OIDC Client (OpenID Connect Authentication)
The OidcClient resource automatically provisions OAuth2/OIDC clients with your identity provider.
Create templates/client.yaml:
apiVersion: homelab.mortenolsen.pro/v1
kind: OidcClient
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
redirectUris:
- path: /oauth/oidc/callback
subdomain: '{{ .Values.subdomain }}'
matchingMode: strict
What it does:
- Creates an OIDC client in your identity provider (e.g., Authentik)
- Generates a Kubernetes secret named
{{ .Release.Name }}-clientcontaining:clientId: The OAuth client IDclientSecret: The OAuth client secretconfiguration: The OIDC provider URL
Using in deployment:
env:
- name: OAUTH_CLIENT_ID
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-client"
key: clientId
- name: OAUTH_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-client"
key: clientSecret
- name: OPENID_PROVIDER_URL
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-client"
key: configuration
2. PostgreSQL Database
The PostgresDatabase resource automatically provisions PostgreSQL databases.
New Version (Recommended):
Create templates/database.yaml:
{{ include "common.database" . }}
Add to values.yaml:
database:
enabled: true
Legacy Version (Deprecated):
The legacy homelab.mortenolsen.pro/v1 API version is deprecated. For new charts, use the common library template which uses the new postgres.homelab.mortenolsen.pro/v1 API version.
What it does:
- Creates a PostgreSQL database with the same name as your release
- Creates a user with appropriate permissions
- Generates a Kubernetes secret named
{{ .Release.Name }}-connectioncontaining:url: Complete PostgreSQL connection URLhost: Database hostnameport: Database portdatabase: Database nameusername: Database usernamepassword: Database password
Using in deployment:
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-connection"
key: url
Note: The secret name changed from {release}-pg-connection (legacy) to {release}-connection (new version). The common library template handles this automatically.
3. Secret Generation
The GenerateSecret resource creates secure random secrets.
Create templates/secret.yaml:
apiVersion: homelab.mortenolsen.pro/v1
kind: GenerateSecret
metadata:
name: "{{ .Release.Name }}-secrets"
spec:
fields:
- name: encryptionkey
encoding: hex # Options: hex, base64, alphanumeric
length: 64 # Length in bytes (before encoding)
- name: apitoken
encoding: base64
length: 32
What it does:
- Generates cryptographically secure random values
- Creates a Kubernetes secret with the specified fields
- Supports different encoding formats for different use cases
Using in deployment:
env:
- name: ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-secrets"
key: encryptionkey
4. External HTTP Service
The ExternalHttpService resource configures ingress routing for your application.
Create templates/external-http-service.yaml:
apiVersion: homelab.mortenolsen.pro/v1
kind: ExternalHttpService
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
subdomain: '{{ .Values.subdomain }}'
destination:
host: '{{ .Release.Name }}.{{ .Release.Namespace }}.svc.cluster.local'
port:
number: 80
What it does:
- Creates ingress routes for your application
- Configures subdomain routing (e.g.,
myapp.yourdomain.com) - Handles TLS termination automatically
- Integrates with your service mesh (if applicable)
Best Practices
1. Naming Conventions
- Use
{{ .Release.Name }}consistently for all resource names - Suffix resource names appropriately:
-data,-secrets,-client,-database
2. Container Configuration
- Always specify health checks (liveness and readiness probes)
- Use named ports (e.g.,
http,grpc) instead of port numbers - Set
revisionHistoryLimit: 0to prevent accumulation of old ReplicaSets
3. Environment Variables
- Never hardcode secrets in values.yaml
- Use secretKeyRef to reference generated secrets
- Group related environment variables together
4. Persistent Storage
- Always use PVCs for stateful data
- Consider storage requirements carefully (start with reasonable defaults)
- Mount data at standard paths for the application
5. OIDC Integration
- Set
ENABLE_SIGNUP: "false"if using OIDC - Enable OIDC signup with
ENABLE_OAUTH_SIGNUP: "true" - Configure email merging if needed with
OAUTH_MERGE_ACCOUNTS_BY_EMAIL
6. Database Usage
- Only include database.yaml if the app needs PostgreSQL
- Applications should support DATABASE_URL environment variable
- Consider connection pooling settings for production
Disabling Applications
To temporarily disable an application, rename its directory with .disabled suffix:
mv apps/charts/my-app apps/charts/my-app.disabled
The ArgoCD ApplicationSet will automatically exclude directories matching *.disabled.
Testing Your Chart
-
Lint your chart:
helm lint apps/charts/my-app -
Render templates locally:
helm template my-app apps/charts/my-app -
Dry run installation:
helm install my-app apps/charts/my-app --dry-run --debug
Deployment Workflow
IMPORTANT: There is no test environment. When creating or modifying applications:
- Make changes directly to the files - The agent will write changes to the actual chart files
- User deploys the changes - After changes are made, the user must deploy them to the cluster
- Debug with kubectl - If issues arise after deployment, agents can use kubectl to:
- Check pod status and logs
- Inspect generated resources
- Verify secret creation
- Troubleshoot configuration issues
Note: Agents cannot deploy applications themselves. They can only:
- Create and modify chart files
- Use kubectl to investigate deployment issues
- Provide debugging assistance and recommendations
Common Patterns
Application with OIDC + Database
For apps requiring both authentication and database:
- Include
client.yamlfor OIDC - Include
database.yamlfor PostgreSQL - Reference both secrets in deployment
Stateless Applications
For simple stateless apps:
- Omit
pvc.yaml - Remove volume mounts from deployment
- Consider using
Deploymentscaling if appropriate
Background Services
For services without web interface:
- Omit
external-http-service.yaml - Omit
client.yaml(no OIDC needed) - Focus on service discovery within cluster
Troubleshooting
Secret Not Found
If secrets are not being created:
- Check that the CRD controller is running
- Verify the
environmentvalue matches your setup - Check controller logs for provisioning errors
OIDC Issues
- Verify redirect URIs match exactly
- Check that the identity provider is accessible
- Ensure the client secret is being properly mounted
Database Connection
- Verify the database operator is running
- Check network policies between namespaces
- Ensure the database server has capacity
Global Values
Applications can access global values through {{ .Values.globals }}:
environment: The deployment environment (e.g., "production", "staging")- Additional values can be added at the root chart level
Maintenance
Updating Images
- Update the tag in
values.yaml:tag: v1.0.0 # Use semantic version tags only - Note: Do not include SHA digests in tags. Immutable digests are automatically added later by Renovate
Renovate Integration
The project uses Renovate for automated dependency updates. Configure in renovate.json5 to:
- Auto-update container images
- Create pull requests for updates
- Group related updates
Backup Considerations
For applications with persistent data:
- Consider implementing backup CronJobs
- Use volume snapshots if available
- Export data regularly for critical applications
Contributing
When adding new applications:
- Follow the existing patterns and conventions
- Document any special requirements in the chart's README
- Consider security implications of all configurations
- Update this document if introducing new patterns
Maintaining This Document
IMPORTANT: When making changes to the project structure, patterns, or custom resources:
- Keep this AGENTS.md file up to date with any changes
- Document new CRDs or custom resources as they are added
- Update examples if the patterns change
- Add new sections for significant new features or patterns
- Ensure all code examples remain accurate and tested
This document serves as the primary reference for creating and maintaining applications in this project. Keeping it current ensures consistency and helps onboard new contributors.