{{- if .Values.migrate.enabled }} {{/* Migrations run here and nowhere else. Not on app startup: three services × N replicas racing the same DDL is how you get a half-applied schema and a crash loop, and it makes "which pod migrated?" unanswerable. A hook runs once, before any new pod starts, and its exit code gates the release. hook-weight -5 puts it ahead of everything else in the pre-install/pre-upgrade phase. hook-delete-policy before-hook-creation keeps the last run's pod around for `kubectl logs` after a failure — the one time you actually want it — and clears it on the next attempt. Deliberately no terminationGracePeriodSeconds: 60 here. Three Deployments carry it; a migration is not one of them. */}} apiVersion: batch/v1 kind: Job metadata: name: {{ include "svcforge.fullname" . }}-migrate labels: {{- include "svcforge.labels" . | nindent 4 }} app.kubernetes.io/component: migrate annotations: "helm.sh/hook": pre-install,pre-upgrade "helm.sh/hook-weight": "-5" "helm.sh/hook-delete-policy": before-hook-creation spec: # 0, not 3. A failed migration must fail the release. Retrying a DDL that just failed # tends to turn one readable error into three, and then a green release on a schema # nobody has looked at. backoffLimit: 0 template: metadata: labels: {{- include "svcforge.labels" . | nindent 8 }} app.kubernetes.io/component: migrate spec: restartPolicy: Never serviceAccountName: {{ include "svcforge.serviceAccountName" (dict "ctx" $ "component" "api") }} {{- with .Values.image.pullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} securityContext: {{- include "svcforge.podSecurityContext" . | nindent 8 }} containers: - name: migrate # Same image as the api, by digest. The migrations that ship are the ones the # code that is about to run was built against — a separate image could drift. image: {{ include "svcforge.image" (dict "ctx" $ "component" "api") }} imagePullPolicy: {{ .Values.image.pullPolicy }} securityContext: {{- include "svcforge.containerSecurityContext" . | nindent 12 }} command: ["python", "-m", "svcforge_core.migrate"] envFrom: - secretRef: name: {{ include "svcforge.secretName" . }} env: {{- include "svcforge.env" . | nindent 12 }} - name: OTEL_SERVICE_NAME value: svcforge-migrate resources: {{- toYaml .Values.migrate.resources | nindent 12 }} volumeMounts: - name: tmp mountPath: /tmp volumes: - name: tmp emptyDir: {} {{- end }}