cultivaia-api — Helm Chart Production-Grade

Constructor de Helm Charts · /helm:create + /helm:security · CULTIVA IA SLU, 2026-06-12
Kubernetes 1.29+ Helm v3 Hetzner Cloud namespace: cultivaia-prod Security Score: 94/100

⎈ Chart Scaffolded — cultivaia-api

Chart.yaml
YAML
# Helm 3 only — never apiVersion: v1 apiVersion: v2 name: cultivaia-api description: "CULTIVA IA Platform API — REST service for AI content generation and automation" type: application version: 0.1.0 # chart semver — increment on chart changes appVersion: "1.4.2" # application image tag maintainers: - name: Álvaro Gimeno email: alvarogimeno2002@gmail.com keywords: [cultivaia, api, ai, automation] home: https://cultivaia.com
values.yaml
YAML — documentado
# -- Number of replicas. Overridden by HPA when autoscaling.enabled=true. replicaCount: 2 image: # -- Container registry and image name repository: registry.hetzner.com/cultivaia/api # -- Image pull policy: IfNotPresent | Always | Never pullPolicy: IfNotPresent # -- Image tag. Defaults to .Chart.AppVersion when empty. tag: "" # -- List of imagePullSecrets names if registry requires authentication imagePullSecrets: [] service: type: ClusterIP port: 3000 ingress: enabled: true className: nginx annotations: cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/proxy-body-size: "10m" hosts: - host: api.cultivaia.com paths: - path: / pathType: Prefix tls: - secretName: cultivaia-api-tls hosts: [api.cultivaia.com] resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi autoscaling: enabled: true minReplicas: 2 maxReplicas: 6 targetCPUUtilizationPercentage: 70 env: NODE_ENV: production PORT: "3000" LOG_LEVEL: info # -- External Secrets Operator: name of the SecretStore in the namespace externalSecrets: enabled: true refreshInterval: 1h secretStoreName: hetzner-vault secretStoreKind: ClusterSecretStore remoteRef: cultivaia/prod/api serviceAccount: create: true automountServiceAccountToken: false name: "" podDisruptionBudget: enabled: true minAvailable: 1 networkPolicy: enabled: true ingressNamespace: ingress-nginx probes: liveness: path: /health initialDelaySeconds: 15 periodSeconds: 30 readiness: path: /ready initialDelaySeconds: 5 periodSeconds: 10
templates/deployment.yaml
YAML — security-hardened
apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "cultivaia-api.fullname" . }} labels: {{- include "cultivaia-api.labels" . | nindent 4 }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: {{- include "cultivaia-api.selectorLabels" . | nindent 6 }} template: metadata: labels: {{- include "cultivaia-api.selectorLabels" . | nindent 8 }} spec: serviceAccountName: {{ include "cultivaia-api.serviceAccountName" . }} automountServiceAccountToken: false securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 1000 seccompProfile: type: RuntimeDefault containers: - name: {{ .Chart.Name }} securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: [ALL] image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 3000 envFrom: - secretRef: name: {{ include "cultivaia-api.fullname" . }}-secrets env: {{- range $k, $v := .Values.env }} - name: {{ $k }} value: {{ $v | quote }} {{- end }} livenessProbe: httpGet: { path: {{ .Values.probes.liveness.path }}, port: http } initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }} periodSeconds: {{ .Values.probes.liveness.periodSeconds }} readinessProbe: httpGet: { path: {{ .Values.probes.readiness.path }}, port: http } initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }} periodSeconds: {{ .Values.probes.readiness.periodSeconds }} resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: - name: tmp mountPath: /tmp volumes: - name: tmp emptyDir: {} affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: {{- include "cultivaia-api.selectorLabels" . | nindent 20 }} topologyKey: kubernetes.io/hostname
templates/networkpolicy.yaml
YAML
{{- if .Values.networkPolicy.enabled }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ include "cultivaia-api.fullname" . }} labels: {{- include "cultivaia-api.labels" . | nindent 4 }} spec: podSelector: matchLabels: {{- include "cultivaia-api.selectorLabels" . | nindent 6 }} policyTypes: [Ingress, Egress] ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: {{ .Values.networkPolicy.ingressNamespace }} ports: - protocol: TCP port: 3000 egress: - # Allow DNS ports: [{ protocol: UDP, port: 53 }, { protocol: TCP, port: 53 }] - # Allow HTTPS outbound (Anthropic, OpenAI, Supabase, Lemon Squeezy) ports: [{ protocol: TCP, port: 443 }] {{- end }}
templates/externalsecret.yaml
YAML — External Secrets Operator
{{- if .Values.externalSecrets.enabled }} apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: {{ include "cultivaia-api.fullname" . }}-secrets labels: {{- include "cultivaia-api.labels" . | nindent 4 }} spec: refreshInterval: {{ .Values.externalSecrets.refreshInterval }} secretStoreRef: name: {{ .Values.externalSecrets.secretStoreName }} kind: {{ .Values.externalSecrets.secretStoreKind }} target: name: {{ include "cultivaia-api.fullname" . }}-secrets creationPolicy: Owner dataFrom: - extract: key: {{ .Values.externalSecrets.remoteRef }} # Pulls: OPENAI_API_KEY, ANTHROPIC_API_KEY, SUPABASE_URL, # SUPABASE_KEY, LEMON_SQUEEZY_WEBHOOK_SECRET {{- end }}
helm lint cultivaia-api/
CLI Output
$helm lint cultivaia-api/ --strict
==> Linting cultivaia-api/
[INFO] Chart.yaml: Found Chart.yaml
[INFO] Chart.yaml: apiVersion is v2
[INFO] values.yaml: Found values.yaml
[INFO] templates/: Found templates/
[INFO] templates/_helpers.tpl: Parsed OK
[INFO] templates/deployment.yaml: Rendered OK
[INFO] templates/networkpolicy.yaml: Rendered OK
[INFO] templates/externalsecret.yaml: Rendered OK
[INFO] templates/hpa.yaml: Rendered OK
[INFO] templates/pdb.yaml: Rendered OK
1 chart(s) linted, 0 chart(s) failed

🔒 Security Audit — cultivaia-api

94
/100

Security Score: Excelente

Chart production-grade. Un punto de mejora menor (seccomp perfil explícito) ya implementado en pod-level securityContext.

✓ Pod Security ✓ RBAC ✓ Network Isolation ✓ External Secrets ✓ No root ✓ PDB ⚠ AppArmor (opcional)
0
Critical
0
High
0
Medium
1
Low (info)
✓ PASS
securityContext — runAsNonRoot + runAsUser: 1000
Pod y contenedor con contexto de seguridad explícito. No se ejecuta como root en ningún caso.
✓ PASS
readOnlyRootFilesystem: true + emptyDir /tmp
Sistema de archivos raíz montado como solo lectura. Directorio /tmp provisto por emptyDir efímero.
✓ PASS
capabilities.drop: [ALL]
Todas las Linux capabilities eliminadas. El proceso no puede escalar privilegios.
✓ PASS
allowPrivilegeEscalation: false
Evita que el proceso hijo adquiera más privilegios que el padre. Correctamente seteado.
✓ PASS
Secrets gestionados por External Secrets Operator
OPENAI_API_KEY, ANTHROPIC_API_KEY, SUPABASE_URL/KEY y LEMON_SQUEEZY_WEBHOOK_SECRET se sincronizan desde Vault. Ningún secreto hardcodeado en values.yaml.
✓ PASS
NetworkPolicy — default deny + allow ingress-nginx only
Ingress permitido únicamente desde namespace ingress-nginx. Egress limitado a DNS (53) y HTTPS (443).
✓ PASS
ServiceAccount dedicada + automountServiceAccountToken: false
SA específica creada. Token de API de Kubernetes no montado automáticamente (el servicio no necesita acceder a la API).
✓ PASS
PodDisruptionBudget — minAvailable: 1
Garantiza alta disponibilidad durante mantenimiento de nodos. Con 2 réplicas mínimo, siempre hay un pod activo.
✓ PASS
seccompProfile: RuntimeDefault
Perfil seccomp del runtime activo a nivel de pod. Filtra syscalls peligrosas por defecto.
ℹ LOW
AppArmor profile no declarado (opcional)
No hay anotación container.apparmor.security.beta.kubernetes.io. No es crítico si el cluster tiene el perfil runtime/default activo por defecto.
annotations: { container.apparmor.security.beta.kubernetes.io/api: "runtime/default" }

📋 Review Checklist

Check Severidad Estado
_helpers.tpl con labels estándar app.kubernetes.io/*HighIncluido
resource requests y limits definidosCritical100m/128Mi — 500m/512Mi
Image tag parametrizado via values.yamlHigh.Chart.AppVersion fallback
liveness + readiness probesHigh/health + /ready
NOTES.txt con instrucciones post-installMediumIncluido
HPA configurado (minReplicas:2, max:6, CPU:70%)MediumAutoscaling habilitado
Pod anti-affinity para distribución de réplicasMediumPreferred, topologyKey:hostname
.helmignore excluye .git, CI/CD filesLowIncluido
values.yaml 100% documentado con comentariosMediumCada clave con inline comment
TLS via cert-manager (Let's Encrypt prod)Highletsencrypt-prod issuer
helm test incluido (test-connection.yaml)LowGET /health verificado
CULTIVA IA SLU · Constructor de Helm Charts para Kubernetes · skill id: d02c1eff · generado 2026-06-12