{{- if .Values.app.enabled }} {{- include "sim.validateSecrets" . }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "sim.fullname" . }}-app namespace: {{ .Release.Namespace }} labels: {{- include "sim.app.labels" . | nindent 4 }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.app.replicaCount }} {{- end }} selector: matchLabels: {{- include "sim.app.selectorLabels" . | nindent 6 }} template: metadata: annotations: checksum/secret: {{ include (print $.Template.BasePath "/secrets-app.yaml") . | sha256sum }} {{- if .Values.branding.enabled }} checksum/config: {{ include (print $.Template.BasePath "/configmap-branding.yaml") . | sha256sum }} {{- end }} {{- with .Values.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "sim.app.selectorLabels" . | nindent 8 }} {{- with .Values.podLabels }} {{- toYaml . | nindent 8 }} {{- end }} spec: {{- with .Values.global.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "sim.serviceAccountName" . }} automountServiceAccountToken: false {{- include "sim.podSecurityContext" .Values.app | nindent 6 }} {{- include "sim.nodeSelector" .Values.app | nindent 6 }} {{- include "sim.tolerations" .Values | nindent 6 }} {{- include "sim.affinity" .Values | nindent 6 }} {{- include "sim.topologySpreadConstraints" .Values.app | nindent 6 }} {{- if .Values.migrations.enabled }} initContainers: - name: migrations image: {{ include "sim.image" (dict "imageRoot" .Values.migrations.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }} imagePullPolicy: {{ .Values.migrations.image.pullPolicy }} command: ["/bin/sh", "-c"] args: - | cd /app/packages/db export DATABASE_URL="{{ include "sim.databaseUrl" . }}" bun run db:migrate envFrom: {{- if .Values.postgresql.enabled }} - secretRef: name: {{ include "sim.postgresqlSecretName" . }} {{- else if .Values.externalDatabase.enabled }} - secretRef: name: {{ include "sim.externalDbSecretName" . }} {{- end }} {{- include "sim.resources" .Values.migrations | nindent 10 }} {{- include "sim.containerSecurityContext" .Values.migrations | nindent 10 }} {{- end }} containers: - name: app image: {{ include "sim.image" (dict "imageRoot" .Values.app.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }} imagePullPolicy: {{ .Values.app.image.pullPolicy }} ports: - name: http containerPort: {{ .Values.app.service.targetPort }} protocol: TCP {{- /* All keys from .Values.app.env are mounted via envFrom (see below). Chart-computed values and operational tunables (.Values.app.envDefaults) are inlined here so they don't leak into the chart-managed Secret and don't need to be mapped when externalSecrets.enabled=true. */}} env: - name: DATABASE_URL value: {{ include "sim.databaseUrl" . | quote }} - name: SOCKET_SERVER_URL value: {{ include "sim.socketServerUrl" . | quote }} - name: OLLAMA_URL value: {{ include "sim.ollamaUrl" . | quote }} - name: PII_URL value: {{ include "sim.piiUrl" . | quote }} {{- /* Skip envDefaults keys that the user has explicitly overridden in app.env with a non-empty value. K8s `env` takes precedence over `envFrom`, so an inline default would otherwise mask the user's Secret-bound value. Also skip envDefaults entirely in existingSecret mode: the pre-existing Secret is the source of truth, and inlining localhost URL defaults would silently shadow keys (NEXT_PUBLIC_APP_URL, BETTER_AUTH_URL, etc.) the user stored in their Secret but left empty in app.env. */}} {{- $appEnv := .Values.app.env | default dict }} {{- $useExistingSecret := and .Values.app.secrets.existingSecret.enabled (not .Values.externalSecrets.enabled) }} {{- if not $useExistingSecret }} {{- range $key, $value := .Values.app.envDefaults | default dict }} {{- $override := index $appEnv $key }} {{- if and (ne (toString $value) "") (ne (toString $value) "") (or (not $override) (eq (toString $override) "") (eq (toString $override) "")) }} - name: {{ $key }} value: {{ $value | quote }} {{- end }} {{- end }} {{- end }} {{- /* In existingSecret mode the chart-managed Secret is not rendered, so non-empty values in app.env would otherwise go nowhere — inline them here so the user's overrides actually reach the pod. Skipped in ESO mode (ESO is the source of truth; inlining would mask the synced value) and in inline mode (values flow through the chart-managed Secret). */}} {{- if and .Values.app.secrets.existingSecret.enabled (not .Values.externalSecrets.enabled) }} {{- $chartComputed := list "DATABASE_URL" "SOCKET_SERVER_URL" "OLLAMA_URL" "PII_URL" }} {{- range $key, $value := $appEnv }} {{- if and (ne (toString $value) "") (ne (toString $value) "") (not (has $key $chartComputed)) }} - name: {{ $key }} value: {{ $value | quote }} {{- end }} {{- end }} {{- end }} {{- if .Values.telemetry.enabled }} {{- $nodeEnv := default (default "production" (index (.Values.app.envDefaults | default dict) "NODE_ENV")) (index (.Values.app.env | default dict) "NODE_ENV") }} # OpenTelemetry configuration - name: OTEL_EXPORTER_OTLP_ENDPOINT value: "http://{{ include "sim.fullname" . }}-otel-collector:4318" - name: OTEL_SERVICE_NAME value: sim-app - name: OTEL_SERVICE_VERSION value: {{ .Chart.AppVersion | quote }} - name: OTEL_RESOURCE_ATTRIBUTES value: "service.name=sim-app,service.version={{ .Chart.AppVersion }},deployment.environment={{ $nodeEnv }}" {{- end }} {{- with .Values.extraEnvVars }} {{- toYaml . | nindent 12 }} {{- end }} envFrom: # App secrets (authentication, encryption keys) - secretRef: name: {{ include "sim.appSecretName" . }} # Database secrets {{- if .Values.postgresql.enabled }} - secretRef: name: {{ include "sim.postgresqlSecretName" . }} {{- else if .Values.externalDatabase.enabled }} - secretRef: name: {{ include "sim.externalDbSecretName" . }} {{- end }} {{- if .Values.app.startupProbe }} startupProbe: {{- toYaml .Values.app.startupProbe | nindent 12 }} {{- end }} {{- if .Values.app.livenessProbe }} livenessProbe: {{- toYaml .Values.app.livenessProbe | nindent 12 }} {{- end }} {{- if .Values.app.readinessProbe }} readinessProbe: {{- toYaml .Values.app.readinessProbe | nindent 12 }} {{- end }} {{- include "sim.resources" .Values.app | nindent 10 }} {{- include "sim.containerSecurityContext" .Values.app | nindent 10 }} {{- $hasBranding := and .Values.branding.enabled (or .Values.branding.files .Values.branding.binaryFiles) }} {{- if or $hasBranding .Values.extraVolumeMounts .Values.app.extraVolumeMounts }} volumeMounts: {{- if $hasBranding }} - name: branding mountPath: {{ .Values.branding.mountPath | default "/app/public/branding" }} readOnly: true {{- end }} {{- with .Values.extraVolumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} {{- with .Values.app.extraVolumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} {{- end }} {{- $hasBranding := and .Values.branding.enabled (or .Values.branding.files .Values.branding.binaryFiles) }} {{- if or $hasBranding .Values.extraVolumes .Values.app.extraVolumes }} volumes: {{- if $hasBranding }} - name: branding configMap: name: {{ include "sim.fullname" . }}-branding {{- end }} {{- with .Values.extraVolumes }} {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.app.extraVolumes }} {{- toYaml . | nindent 8 }} {{- end }} {{- end }} {{- end }}