update
diff --git a/charts/openproject/templates/_helpers.tpl b/charts/openproject/templates/_helpers.tpl
new file mode 100644
index 0000000..0a6e284
--- /dev/null
+++ b/charts/openproject/templates/_helpers.tpl
@@ -0,0 +1,152 @@
+{{/*
+Returns the OpenProject image to be used including the respective registry and image tag.
+*/}}
+{{- define "openproject.image" -}}
+{{ .Values.image.registry }}/{{ .Values.image.repository }}{{ if .Values.image.sha256 }}@sha256:{{ .Values.image.sha256 }}{{ else }}:{{ .Values.image.tag }}{{ end }}
+{{- end -}}
+
+{{/*
+Returns the OpenProject image pull secrets, if any are defined
+*/}}
+{{- define "openproject.imagePullSecrets" -}}
+{{- if or .Values.imagePullSecrets .Values.global.imagePullSecrets }}
+imagePullSecrets:
+  {{- range (coalesce .Values.imagePullSecrets .Values.global.imagePullSecrets) }}
+  - name: "{{ . }}"
+  {{- end }}
+{{- end }}
+{{- end -}}
+
+{{/*
+Yields the configured container security context if enabled.
+
+Allows writing to the container file system in development mode
+This way the OpenProject container works without mounted tmp volumes
+which may not work correctly in local development clusters.
+*/}}
+{{- define "openproject.containerSecurityContext" }}
+{{- if .Values.containerSecurityContext.enabled }}
+securityContext:
+  {{-
+    mergeOverwrite
+      (omit .Values.containerSecurityContext "enabled" | deepCopy)
+      (dict "readOnlyRootFilesystem" (and
+        (not .Values.develop)
+        (get .Values.containerSecurityContext "readOnlyRootFilesystem")
+      ))
+    | toYaml
+    | nindent 2
+  }}
+{{- end }}
+{{- end }}
+
+{{/* Yields the configured pod security context if enabled. */}}
+{{- define "openproject.podSecurityContext" }}
+{{- if .Values.podSecurityContext.enabled }}
+securityContext:
+  {{ omit .Values.podSecurityContext "enabled" | toYaml | nindent 2 | trim }}
+{{- end }}
+{{- end }}
+
+
+{{- define "openproject.useTmpVolumes" -}}
+{{- if ne .Values.openproject.useTmpVolumes nil -}}
+  {{- .Values.openproject.useTmpVolumes -}}
+{{- else -}}
+  {{- (not .Values.develop) -}}
+{{- end -}}
+{{- end -}}
+
+{{- define "openproject.tmpVolumeMounts" -}}
+{{- if eq (include "openproject.useTmpVolumes" .) "true" }}
+- mountPath: /tmp
+  name: tmp
+- mountPath: /app/tmp
+  name: app-tmp
+{{- end }}
+{{- end -}}
+
+{{- define "openproject.tmpVolumeSpec" -}}
+{{- if eq (include "openproject.useTmpVolumes" .) "true" }}
+- name: tmp
+  # we can't use emptyDir due to the sticky bit issue
+  # see: https://github.com/kubernetes/kubernetes/issues/110835
+  ephemeral:
+    volumeClaimTemplate:
+      spec:
+        accessModes: ["ReadWriteOnce"]
+        resources:
+          requests:
+            storage: {{ .Values.openproject.tmpVolumesStorage }}
+- name: app-tmp
+  # we can't use emptyDir due to the sticky bit / world writable issue
+  # see: https://github.com/kubernetes/kubernetes/issues/110835
+  ephemeral:
+    volumeClaimTemplate:
+      spec:
+        accessModes: ["ReadWriteOnce"]
+        resources:
+          requests:
+            storage: {{ .Values.openproject.tmpVolumesStorage }}
+{{- end }}
+{{- end -}}
+
+{{- define "openproject.envFrom" -}}
+- secretRef:
+    name: {{ include "common.names.fullname" . }}-core
+{{- if .Values.openproject.oidc.enabled }}
+- secretRef:
+    name: {{ include "common.names.fullname" . }}-oidc
+{{- end }}
+{{- if .Values.s3.enabled }}
+- secretRef:
+    name: {{ include "common.names.fullname" . }}-s3
+{{- end }}
+{{- if eq .Values.openproject.cache.store "memcache" }}
+- secretRef:
+    name: {{ include "common.names.fullname" . }}-memcached
+{{- end }}
+{{- if .Values.environment }}
+- secretRef:
+    name: {{ include "common.names.fullname" . }}-environment
+{{- end }}
+{{- if .Values.openproject.extraEnvVarsSecret }}
+- secretRef:
+    name: {{ .Values.openproject.extraEnvVarsSecret }}
+{{- end }}
+{{- if .Values.openproject.oidc.extraOidcSealedSecret }}
+- secretRef:
+    name: {{ .Values.openproject.oidc.extraOidcSealedSecret }}
+{{- end }}
+{{- end }}
+
+{{- define "openproject.env" -}}
+{{- if .Values.egress.tls.rootCA.fileName }}
+- name: SSL_CERT_FILE
+  value: "/etc/ssl/certs/custom-ca.pem"
+{{- end }}
+{{- if .Values.postgresql.auth.existingSecret }}
+- name: OPENPROJECT_DB_PASSWORD
+  valueFrom:
+    secretKeyRef:
+      name: {{ .Values.postgresql.auth.existingSecret }}
+      key: {{ .Values.postgresql.auth.secretKeys.userPasswordKey }}
+{{- else if .Values.postgresql.auth.password }}
+- name: OPENPROJECT_DB_PASSWORD
+  value: {{ .Values.postgresql.auth.password }}
+{{- else }}
+- name: OPENPROJECT_DB_PASSWORD
+  valueFrom:
+    secretKeyRef:
+      name: {{ include "common.names.dependency.fullname" (dict "chartName" "postgresql" "chartValues" .Values.postgresql "context" $) }}
+      key: {{ .Values.postgresql.auth.secretKeys.userPasswordKey }}
+{{- end }}
+{{- end }}
+
+{{- define "openproject.envChecksums" }}
+# annotate pods with env value checksums so changes trigger re-deployments
+{{/* If I knew how to map and reduce a range in helm I would do that and use a single checksum. But here we are. */}}
+{{- range $suffix := list "core" "memcached" "oidc" "s3" "environment" }}
+checksum/env-{{ $suffix }}: {{ include (print $.Template.BasePath "/secret_" $suffix ".yaml") $ | sha256sum }}
+{{- end }}
+{{- end }}