feat(helm): add cloudhost-app Helm chart for all runtimes
- deployment.yaml with imagePullSecrets, health probes, WordPress volumes - db-deployment.yaml for PostgreSQL/MySQL with readiness/liveness probes - ingress.yaml with cert-manager TLS - registry-pull-secret.yaml for insecure registries - PVCs and secrets with helm.sh/resource-policy: keep - _helpers.tpl with shared template functions - values.yaml with comprehensive defaults
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: cloudhost-app
|
||||
description: CloudHost PaaS — generic application chart (Node.js, Laravel, WordPress)
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "1.0.0"
|
||||
@@ -0,0 +1,81 @@
|
||||
{{/*
|
||||
CloudHost App — template helpers
|
||||
*/}}
|
||||
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "cloudhost-app.name" -}}
|
||||
{{- .Values.app.name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Namespace for the release.
|
||||
*/}}
|
||||
{{- define "cloudhost-app.namespace" -}}
|
||||
{{- .Values.app.namespace }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "cloudhost-app.labels" -}}
|
||||
app: {{ include "cloudhost-app.name" . }}
|
||||
app.kubernetes.io/managed-by: cloudhost-helm
|
||||
runtime: {{ .Values.app.runtime }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "cloudhost-app.selectorLabels" -}}
|
||||
app: {{ include "cloudhost-app.name" . }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Database name (app name with dashes replaced by underscores)
|
||||
*/}}
|
||||
{{- define "cloudhost-app.dbName" -}}
|
||||
{{- .Values.app.name | replace "-" "_" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Database deployment name
|
||||
*/}}
|
||||
{{- define "cloudhost-app.dbDeploymentName" -}}
|
||||
{{- printf "%s-db" .Values.app.name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Database image — auto-computed from type + version if not explicitly set
|
||||
*/}}
|
||||
{{- define "cloudhost-app.dbImage" -}}
|
||||
{{- if .Values.database.image }}
|
||||
{{- .Values.database.image }}
|
||||
{{- else if eq .Values.database.type "postgresql" }}
|
||||
{{- printf "postgres:%s-alpine" .Values.database.version }}
|
||||
{{- else }}
|
||||
{{- printf "mysql:%s" .Values.database.version }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Database port
|
||||
*/}}
|
||||
{{- define "cloudhost-app.dbPort" -}}
|
||||
{{- if eq .Values.database.type "postgresql" }}5432{{- else }}3306{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Database data mount path (volume mount target)
|
||||
*/}}
|
||||
{{- define "cloudhost-app.dbDataPath" -}}
|
||||
{{- if eq .Values.database.type "postgresql" }}/var/lib/postgresql/data{{- else }}/var/lib/mysql{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
PostgreSQL PGDATA path — must be a subdirectory of the mount to avoid "initdb: directory not empty" errors
|
||||
*/}}
|
||||
{{- define "cloudhost-app.pgDataDir" -}}
|
||||
/var/lib/postgresql/data/pgdata
|
||||
{{- end }}
|
||||
@@ -0,0 +1,102 @@
|
||||
{{- if .Values.database.enabled }}
|
||||
{{- $name := include "cloudhost-app.name" . -}}
|
||||
{{- $ns := include "cloudhost-app.namespace" . -}}
|
||||
{{- $dbName := include "cloudhost-app.dbDeploymentName" . -}}
|
||||
{{- $dbPort := include "cloudhost-app.dbPort" . -}}
|
||||
{{- $dbData := include "cloudhost-app.dbDataPath" . -}}
|
||||
{{- $dbImg := include "cloudhost-app.dbImage" . -}}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ $dbName }}
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
app: {{ $dbName }}
|
||||
{{- include "cloudhost-app.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ $dbName }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ $dbName }}
|
||||
spec:
|
||||
containers:
|
||||
- name: {{ $dbName }}
|
||||
image: {{ $dbImg }}
|
||||
ports:
|
||||
- containerPort: {{ include "cloudhost-app.dbPort" . | int }}
|
||||
env:
|
||||
{{- if eq .Values.database.type "postgresql" }}
|
||||
- name: PGDATA
|
||||
value: {{ include "cloudhost-app.pgDataDir" . }}
|
||||
- name: POSTGRES_DB
|
||||
value: {{ include "cloudhost-app.dbName" . }}
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ $name }}-db-secret
|
||||
key: username
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ $name }}-db-secret
|
||||
key: password
|
||||
{{- else }}
|
||||
- name: MYSQL_DATABASE
|
||||
value: {{ include "cloudhost-app.dbName" . }}
|
||||
- name: MYSQL_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ $name }}-db-secret
|
||||
key: username
|
||||
- name: MYSQL_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ $name }}-db-secret
|
||||
key: password
|
||||
- name: MYSQL_ROOT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ $name }}-db-secret
|
||||
key: password
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: db-storage
|
||||
mountPath: {{ $dbData }}
|
||||
resources:
|
||||
requests:
|
||||
cpu: {{ .Values.database.resources.cpuRequest | quote }}
|
||||
memory: {{ .Values.database.resources.memoryRequest | quote }}
|
||||
limits:
|
||||
cpu: {{ .Values.database.resources.cpuLimit | quote }}
|
||||
memory: {{ .Values.database.resources.memoryLimit | quote }}
|
||||
readinessProbe:
|
||||
{{- if eq .Values.database.type "postgresql" }}
|
||||
exec:
|
||||
command: ["pg_isready", "-U", {{ .Values.database.username | quote }}]
|
||||
{{- else }}
|
||||
exec:
|
||||
command: ["mysqladmin", "ping", "-h", "127.0.0.1"]
|
||||
{{- end }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
failureThreshold: 6
|
||||
livenessProbe:
|
||||
{{- if eq .Values.database.type "postgresql" }}
|
||||
exec:
|
||||
command: ["pg_isready", "-U", {{ .Values.database.username | quote }}]
|
||||
{{- else }}
|
||||
exec:
|
||||
command: ["mysqladmin", "ping", "-h", "127.0.0.1"]
|
||||
{{- end }}
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 5
|
||||
volumes:
|
||||
- name: db-storage
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ $dbName }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,20 @@
|
||||
{{- if .Values.database.enabled }}
|
||||
{{- $ns := include "cloudhost-app.namespace" . -}}
|
||||
{{- $dbName := include "cloudhost-app.dbDeploymentName" . -}}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ $dbName }}
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
app: {{ $dbName }}
|
||||
{{- include "cloudhost-app.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/resource-policy": keep
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.database.storageSize | quote }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,17 @@
|
||||
{{- if .Values.database.enabled }}
|
||||
{{- $name := include "cloudhost-app.name" . -}}
|
||||
{{- $ns := include "cloudhost-app.namespace" . -}}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ $name }}-db-secret
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
{{- include "cloudhost-app.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/resource-policy": keep
|
||||
type: Opaque
|
||||
data:
|
||||
username: {{ .Values.database.username | b64enc | quote }}
|
||||
password: {{ .Values.database.password | b64enc | quote }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,21 @@
|
||||
{{- if .Values.database.enabled }}
|
||||
{{- $ns := include "cloudhost-app.namespace" . -}}
|
||||
{{- $dbName := include "cloudhost-app.dbDeploymentName" . -}}
|
||||
{{- $dbPort := include "cloudhost-app.dbPort" . -}}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ $dbName }}
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
app: {{ $dbName }}
|
||||
{{- include "cloudhost-app.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: {{ $dbName }}
|
||||
ports:
|
||||
- port: {{ include "cloudhost-app.dbPort" . | int }}
|
||||
targetPort: {{ include "cloudhost-app.dbPort" . | int }}
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
@@ -0,0 +1,124 @@
|
||||
{{- $name := include "cloudhost-app.name" . -}}
|
||||
{{- $ns := include "cloudhost-app.namespace" . -}}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ $name }}
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
{{- include "cloudhost-app.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
kubernetes.io/change-cause: {{ .Values.changeCause | default (printf "Deploy %s" .Values.app.image) | quote }}
|
||||
spec:
|
||||
revisionHistoryLimit: 10
|
||||
replicas: {{ .Values.app.replicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "cloudhost-app.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "cloudhost-app.labels" . | nindent 8 }}
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: registry-pull-secret
|
||||
containers:
|
||||
- name: {{ $name }}
|
||||
image: {{ .Values.app.image | quote }}
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: {{ .Values.app.port }}
|
||||
{{- if and .Values.envVars (gt (len .Values.envVars) 0) }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ $name }}-env
|
||||
{{- end }}
|
||||
env:
|
||||
{{- if and .Values.database.enabled (eq .Values.database.type "postgresql") }}
|
||||
- name: DB_HOST
|
||||
value: {{ include "cloudhost-app.dbDeploymentName" . }}
|
||||
- name: DB_PORT
|
||||
value: "5432"
|
||||
- name: DB_NAME
|
||||
value: {{ include "cloudhost-app.dbName" . }}
|
||||
- name: DB_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ $name }}-db-secret
|
||||
key: username
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ $name }}-db-secret
|
||||
key: password
|
||||
- name: DATABASE_URL
|
||||
value: "postgresql://$(DB_USER):$(DB_PASSWORD)@{{ include "cloudhost-app.dbDeploymentName" . }}:5432/{{ include "cloudhost-app.dbName" . }}"
|
||||
{{- end }}
|
||||
{{- if and .Values.database.enabled (eq .Values.database.type "mysql") }}
|
||||
- name: DB_HOST
|
||||
value: {{ include "cloudhost-app.dbDeploymentName" . }}
|
||||
- name: DB_PORT
|
||||
value: "3306"
|
||||
- name: DB_NAME
|
||||
value: {{ include "cloudhost-app.dbName" . }}
|
||||
- name: DB_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ $name }}-db-secret
|
||||
key: username
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ $name }}-db-secret
|
||||
key: password
|
||||
- name: DATABASE_URL
|
||||
value: "mysql://$(DB_USER):$(DB_PASSWORD)@{{ include "cloudhost-app.dbDeploymentName" . }}:3306/{{ include "cloudhost-app.dbName" . }}"
|
||||
{{- end }}
|
||||
{{- /* WordPress-specific env vars (official image expects these) */}}
|
||||
{{- if and .Values.wordpress.enabled .Values.database.enabled }}
|
||||
- name: WORDPRESS_DB_HOST
|
||||
value: "{{ include "cloudhost-app.dbDeploymentName" . }}:{{ include "cloudhost-app.dbPort" . }}"
|
||||
- name: WORDPRESS_DB_NAME
|
||||
value: {{ include "cloudhost-app.dbName" . }}
|
||||
- name: WORDPRESS_DB_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ $name }}-db-secret
|
||||
key: username
|
||||
- name: WORDPRESS_DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ $name }}-db-secret
|
||||
key: password
|
||||
- name: WORDPRESS_TABLE_PREFIX
|
||||
value: "wp_"
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
cpu: {{ .Values.resources.cpuRequest | quote }}
|
||||
memory: {{ .Values.resources.memoryRequest | quote }}
|
||||
limits:
|
||||
cpu: {{ .Values.resources.cpuLimit | quote }}
|
||||
memory: {{ .Values.resources.memoryLimit | quote }}
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.app.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.app.port }}
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
failureThreshold: 5
|
||||
{{- if .Values.wordpress.enabled }}
|
||||
volumeMounts:
|
||||
- name: wp-content
|
||||
mountPath: /var/www/html/wp-content
|
||||
{{- end }}
|
||||
{{- if .Values.wordpress.enabled }}
|
||||
volumes:
|
||||
- name: wp-content
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ $name }}-wp-content
|
||||
{{- end }}
|
||||
@@ -0,0 +1,31 @@
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- $name := include "cloudhost-app.name" . -}}
|
||||
{{- $ns := include "cloudhost-app.namespace" . -}}
|
||||
{{- $host := printf "%s.%s" (default $name .Values.ingress.subdomain) .Values.ingress.domain -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $name }}
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
{{- include "cloudhost-app.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: {{ .Values.ingress.clusterIssuer | quote }}
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: {{ $host }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ $name }}
|
||||
port:
|
||||
number: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ $host }}
|
||||
secretName: {{ $name }}-tls
|
||||
{{- end }}
|
||||
@@ -0,0 +1,13 @@
|
||||
{{- $name := include "cloudhost-app.name" . -}}
|
||||
{{- $ns := include "cloudhost-app.namespace" . -}}
|
||||
{{- $registryUrl := .Values.registry.url | default "localhost:30500" -}}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: registry-pull-secret
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
{{- include "cloudhost-app.labels" . | nindent 4 }}
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
data:
|
||||
.dockerconfigjson: {{ printf `{"auths":{"%s":{"auth":""}}}` $registryUrl | b64enc | quote }}
|
||||
@@ -0,0 +1,16 @@
|
||||
{{- if and .Values.envVars (gt (len .Values.envVars) 0) }}
|
||||
{{- $name := include "cloudhost-app.name" . -}}
|
||||
{{- $ns := include "cloudhost-app.namespace" . -}}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ $name }}-env
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
{{- include "cloudhost-app.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
data:
|
||||
{{- range $key, $val := .Values.envVars }}
|
||||
{{ $key }}: {{ $val | b64enc | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,17 @@
|
||||
{{- $name := include "cloudhost-app.name" . -}}
|
||||
{{- $ns := include "cloudhost-app.namespace" . -}}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ $name }}
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
{{- include "cloudhost-app.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
{{- include "cloudhost-app.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: {{ .Values.app.port }}
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,19 @@
|
||||
{{- if .Values.wordpress.enabled }}
|
||||
{{- $name := include "cloudhost-app.name" . -}}
|
||||
{{- $ns := include "cloudhost-app.namespace" . -}}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ $name }}-wp-content
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
{{- include "cloudhost-app.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/resource-policy": keep
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.wordpress.wpContentStorageSize | quote }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,59 @@
|
||||
# ────────────────────────────────────────────────────────────
|
||||
# CloudHost Application — default Helm values
|
||||
# These are overridden per-app at install/upgrade time.
|
||||
# ────────────────────────────────────────────────────────────
|
||||
|
||||
# ── Application ──────────────────────────────────────────
|
||||
app:
|
||||
name: my-app
|
||||
namespace: user-default
|
||||
runtime: nodejs # nodejs | laravel | wordpress
|
||||
image: "" # e.g. registry.example.com/my-app:v1
|
||||
port: 3000
|
||||
replicas: 1
|
||||
|
||||
# ── Resources ────────────────────────────────────────────
|
||||
resources:
|
||||
cpuRequest: "100m"
|
||||
cpuLimit: "500m"
|
||||
memoryRequest: "128Mi"
|
||||
memoryLimit: "512Mi"
|
||||
|
||||
# ── Environment variables (injected via Secret) ─────────
|
||||
envVars: {}
|
||||
# KEY: value
|
||||
|
||||
# ── Ingress ──────────────────────────────────────────────
|
||||
ingress:
|
||||
enabled: true
|
||||
subdomain: "" # <subdomain>.<domain>
|
||||
domain: "apps.cloudhost.ir"
|
||||
clusterIssuer: letsencrypt-prod
|
||||
|
||||
# ── Database (MySQL or PostgreSQL) ───────────────────────
|
||||
database:
|
||||
enabled: false
|
||||
type: postgresql # mysql | postgresql
|
||||
version: "16" # postgres version or mysql version
|
||||
image: "" # auto-computed if empty
|
||||
port: 5432 # 5432 for pg, 3306 for mysql
|
||||
username: appuser
|
||||
password: ""
|
||||
storageSize: "1Gi"
|
||||
resources:
|
||||
cpuRequest: "100m"
|
||||
cpuLimit: "500m"
|
||||
memoryRequest: "256Mi"
|
||||
memoryLimit: "512Mi"
|
||||
|
||||
# ── WordPress-specific ───────────────────────────────────
|
||||
wordpress:
|
||||
enabled: false
|
||||
wpContentStorageSize: "2Gi"
|
||||
|
||||
# ── Change metadata ─────────────────────────────────────
|
||||
changeCause: ""
|
||||
|
||||
# ── Registry (for imagePullSecret) ──────────────────────
|
||||
registry:
|
||||
url: "localhost:30500"
|
||||
Reference in New Issue
Block a user