diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000000000000000000000000000000000000..929023b75883833d9e99e2cf8ad00650d20d223a
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,252 @@
+pipeline {
+    agent {
+       node {
+           label 'ozgcloud-jenkins-build-agent'
+        }
+    }
+
+    environment {
+        BLUE_OCEAN_URL = "https://jenkins.infra.ozg-cloud.systems/job/ozgcloud-app-elster-transfer/job/${env.BRANCH_NAME}/${env.BUILD_NUMBER}/"
+        RELEASE_REGEX = /\d+.\d+.\d+/
+        SNAPSHOT_REGEX = /\d+.\d+.\d+-SNAPSHOT/
+        FAILED_STAGE = ""
+        SH_SUCCESS_STATUS_CODE = 0
+    }
+
+    options {
+        timeout(time: 1, unit: 'HOURS')
+        disableConcurrentBuilds()
+        buildDiscarder(logRotator(numToKeepStr: '5'))
+    }
+
+    stages {
+        stage('Check Version') {
+            steps {
+                script {
+                    FAILED_STAGE = env.STAGE_NAME
+                    def helmChartVersion = getHelmChartVersion()
+
+                    if(isReleaseBranch()){
+                        if ( !(helmChartVersion ==~ RELEASE_REGEX) ) {
+                            error("Keine Release Version für Branch ${env.BRANCH_NAME}.")
+                        }
+                    } else {
+                        if ( !(helmChartVersion ==~ SNAPSHOT_REGEX) ) {
+                            error("Keine Snapshot Version für Branch ${env.BRANCH_NAME}.")
+                        }
+                    }
+                }
+            }
+        }
+
+        stage('Test HelmChart') {
+            steps {
+                script {
+                    FAILED_STAGE=env.STAGE_NAME
+
+                    sh "./run_helm_test.sh"
+                }
+            }
+        }
+
+        stage('Build HelmChart') {
+            steps {
+                script {
+                    FAILED_STAGE=env.STAGE_NAME
+                    HELM_CHART_VERSION = generateHelmChartBuildVersion()
+
+                    dir('elster-transfer') {
+                        sh "helm package --version=${HELM_CHART_VERSION} ."
+                    }
+                }
+            }
+        }
+
+        stage('Deploy HelmChart') {
+            steps {
+                script {
+                    FAILED_STAGE=env.STAGE_NAME
+
+                    dir('elster-transfer') {
+                        deployHelmChart("elster-transfer", HELM_CHART_VERSION)
+                    }
+                }
+            }
+        } 
+
+        stage('Trigger Dev rollout') {
+            when {
+                branch 'master'
+            }
+            steps {
+                script {
+                    FAILED_STAGE = env.STAGE_NAME
+
+                    cloneGitopsRepo()
+
+                    setNewDevVersion()
+
+                    pushDevGitopsRepo()
+                }
+            }
+        }
+        stage('Trigger Test rollout') {
+            when {
+                branch 'release'
+            }	
+            	
+            steps {	
+                script {	
+                    FAILED_STAGE = env.STAGE_NAME
+
+                    cloneGitopsRepo()
+
+                    setNewTestVersion()
+
+                    pushTestGitopsRepo()
+                }
+            }	
+        }
+    }
+
+    post {
+        failure {
+            script {
+                if (isMasterBranch() || isReleaseBranch()) {
+                    sendFailureMessage()
+                }
+            }
+        }
+    }
+}
+
+
+Boolean isReleaseBranch() {
+    return env.BRANCH_NAME == 'release'
+}
+
+Boolean isMasterBranch() {
+    return env.BRANCH_NAME == 'master'
+}
+
+void deployHelmChart(String helmChartName, String helmChartVersion) {       
+    withCredentials([usernamePassword(credentialsId: 'jenkins-nexus-login', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]){
+        if (isReleaseBranch()) {
+            result = sh script: '''curl -u $USERNAME:$PASSWORD https://nexus.ozg-sh.de/service/rest/v1/components?repository=ozg-base-apps -F file=@'''+helmChartName+'''-'''+helmChartVersion+'''.tgz''', returnStdout: true
+        }
+        else {
+            result = sh script: '''curl -u $USERNAME:$PASSWORD https://nexus.ozg-sh.de/service/rest/v1/components?repository=ozg-base-apps-snapshot -F file=@'''+helmChartName+'''-'''+helmChartVersion+'''.tgz''', returnStdout: true
+        }
+
+        if (result != '') {
+            error(result)
+        }
+    }
+}
+
+String generateHelmChartBuildVersion() {
+    def chartVersion = getHelmChartVersion()
+
+    if (isMasterBranch()) {
+        chartVersion += "-${env.GIT_COMMIT.take(7)}"
+    }
+    else if (!isReleaseBranch()) {
+        chartVersion += "-${env.BRANCH_NAME}"
+    }
+
+    return chartVersion.replaceAll("_", "-")
+}
+
+
+Void configureGit() {
+    final email = "jenkins@ozg-sh.de"
+    final name = "jenkins"
+
+    dir("gitops") {
+        sh "git config user.email '${email}'"
+        sh "git config user.name '${name}'"
+    }
+}
+
+Void cloneGitopsRepo() {
+    withCredentials([usernamePassword(credentialsId: 'jenkins-gitea-access-token', passwordVariable: 'TOKEN', usernameVariable: 'USER')]) {
+        sh 'git clone https://${USER}:${TOKEN}@git.ozg-sh.de/ozgcloud-devops/gitops.git'
+    }
+
+    configureGit()
+}
+
+Void setNewDevVersion() {
+    setNewElsterTransferGitopsVersion("dev")
+}
+
+Void setNewTestVersion() {
+    setNewElsterTransferGitopsVersion("test")
+}
+
+Void setNewElsterTransferGitopsVersion(String environment) {
+    dir("gitops") {
+        def envFile = "${environment}/application/values/elster-transfer-values.yaml"
+        def envVersions = readYaml file: envFile
+
+        envVersions.elster_transfer.helm.version = HELM_CHART_VERSION
+
+        writeYaml file: envFile, data: envVersions, overwrite: true
+    }
+}
+
+Void pushDevGitopsRepo() {
+    pushNewGitopsVersion('dev')
+}
+
+Void pushTestGitopsRepo() {
+    pushNewGitopsVersion('test')
+}
+
+Void pushNewGitopsVersion(String environment) {
+    dir('gitops') {
+        if (!hasGitopsValuesFileChanged(environment)) {
+            return
+        }
+
+        withCredentials([usernamePassword(credentialsId: 'jenkins-gitea-access-token', passwordVariable: 'TOKEN', usernameVariable: 'USER')]) {
+            sh "git add ${environment}/application/values/elster-transfer-values.yaml"
+
+            sh "git commit -m 'jenkins rollout ${environment} elster-transfer version ${HELM_CHART_VERSION}'"
+            sh 'git push https://${USER}:${TOKEN}@git.ozg-sh.de/ozgcloud-devops/gitops.git'
+        }
+    }
+}
+
+Boolean hasGitopsValuesFileChanged(String environment) {
+    return sh (script: "git status | grep '${environment}/application/values/elster-transfer-values.yaml'", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer
+}
+
+Void sendFailureMessage() {
+    def room = ''
+    def data = """{"msgtype":"m.text", \
+                    "body":"Elster-Transfer: Build Failed. Stage: ${FAILED_STAGE} Build-ID: ${env.BUILD_NUMBER} Link: ${BLUE_OCEAN_URL}", \
+                    "format": "org.matrix.custom.html", \
+                    "formatted_body":"Elster-Transfer: Build Failed. Stage: ${FAILED_STAGE} Build-ID: <a href='${BLUE_OCEAN_URL}'>${env.BUILD_NUMBER}</a>"}"""
+       
+    if (isMasterBranch()) {
+        room = "!iQPAvQIiRwRpNOszjw:matrix.ozg-sh.de"
+    }
+    else if (isReleaseBranch()) {
+        room = "!oWZpUGTFsxkJIYNfYg:matrix.ozg-sh.de"
+    }
+
+    sh "curl -XPOST -H 'authorization: Bearer ${getElementAccessToken()}' -d '${data}' https://matrix.ozg-sh.de/_matrix/client/v3/rooms/$room/send/m.room.message"
+}
+
+String getElementAccessToken() {
+    withCredentials([string(credentialsId: 'element-login-json', variable: 'LOGIN_JSON')]) {
+        return readJSON ( text: sh (script: '''curl -XPOST -d \"$LOGIN_JSON\" https://matrix.ozg-sh.de/_matrix/client/v3/login''', returnStdout: true)).access_token
+    }
+}
+
+String getHelmChartVersion() {
+    def chartEnv = readYaml file: "elster-transfer/Chart.yaml"
+
+    return chartEnv.version
+}
\ No newline at end of file
diff --git a/elster-transfer/Chart.yaml b/elster-transfer/Chart.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f3e654bdb334f305e3e48fc243ec55c1df1b931f
--- /dev/null
+++ b/elster-transfer/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v2
+name: elster-transfer
+description: A Helm chart for Elster-Transfer
+type: application
+version: 0.1.0-SNAPSHOT
+appVersion: "24.04.0"
diff --git a/elster-transfer/lint-values.yaml b/elster-transfer/lint-values.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..af06854a7559b44c9c85df9fed88c52e6d61a974
--- /dev/null
+++ b/elster-transfer/lint-values.yaml
@@ -0,0 +1,4 @@
+imagePullSecret: ozgcloud-image-pull-secret
+
+networkPolicy:
+  dnsServerNamespace: test-dns-namespace
\ No newline at end of file
diff --git a/elster-transfer/templates/_helpers.tpl b/elster-transfer/templates/_helpers.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..3a13fe3d710ea586853571c198c99d941661b23e
--- /dev/null
+++ b/elster-transfer/templates/_helpers.tpl
@@ -0,0 +1,55 @@
+{{/* vim: set filetype=mustache: */}}
+
+{{/* Chart: Name + Version */}}
+{{- define "app.chart" -}}
+{{- if gt (len (printf "%s-%s" .Chart.Name .Chart.Version)) 63 -}}
+{{- fail (printf ".Chart.Name-.Chart.Version %s-%s ist zu lang (max. 63 Zeichen)" .Chart.Name .Chart.Version) -}}
+{{- end -}}
+{{ printf "%s-%s" .Chart.Name .Chart.Version }}
+{{- end -}}
+
+{{/* Managed-by -> On Helm, this value is always Helm */}}
+{{- define "app.managedBy" -}}
+{{- if gt (len (.Release.Service)) 63 -}}
+{{- fail (printf ".Release.Service %s ist zu lang (max. 63 Zeichen)" .Release.Service) -}}
+{{- end -}}
+{{ printf "%s" .Release.Service }}
+{{- end -}}
+
+{{/* Default Labels: Helm recommended best-practice labels https://helm.sh/docs/chart_best_practices/labels/ */}}
+{{- define "app.defaultLabels" }}
+app.kubernetes.io/instance: elster-transfer
+app.kubernetes.io/managed-by: {{ include "app.managedBy" . }}
+app.kubernetes.io/name: {{ .Release.Name }}
+app.kubernetes.io/namespace: {{ .Release.Namespace }}
+app.kubernetes.io/part-of: ozgcloud
+app.kubernetes.io/version: {{ .Chart.Version }}
+helm.sh/chart: {{ include "app.chart" . }}
+{{- end -}}
+
+{{- define "app.matchLabels" }}
+app.kubernetes.io/name: {{ .Release.Name }}
+app.kubernetes.io/namespace: {{ .Release.Namespace }}
+{{- end -}}
+
+{{- define "app.serviceAccountName" -}}
+{{ printf "%s" ( (.Values.serviceAccount).name | default "elster-transfer-service-account" ) }}
+{{- end -}}
+
+{{- define "app.getCustomList" -}}
+{{- with (.Values.env).customList -}}
+{{- if kindIs "map" . -}}
+{{ include "app.dictToList" . }}
+{{- else if kindIs "slice" . -}}
+{{ . | toYaml }}
+{{- end -}}
+{{- end -}}
+{{- end -}}
+
+{{- define "app.dictToList" -}}
+{{- $customList := list -}}
+{{- range $key, $value := . -}}
+{{- $customList = append $customList (dict "name" $key "value" $value) }}
+{{- end -}}
+{{- $customList | toYaml -}}
+{{- end -}}
\ No newline at end of file
diff --git a/elster-transfer/templates/deployment.yaml b/elster-transfer/templates/deployment.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5bb04063b5a784c2d9caea016b1c835a48ac1bbe
--- /dev/null
+++ b/elster-transfer/templates/deployment.yaml
@@ -0,0 +1,83 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ .Release.Name }}
+  namespace: {{ .Release.Namespace }}
+  labels:
+    {{- include "app.defaultLabels" . | indent 4 }}
+spec:
+  progressDeadlineSeconds: 600
+  replicas: {{ .Values.replicaCount }}
+  revisionHistoryLimit: 10
+  selector:
+    matchLabels:
+      {{- include "app.matchLabels" . | indent 6 }}
+  strategy:
+    type: Recreate
+  template:
+    metadata:
+      labels:
+        {{- include "app.defaultLabels" . | indent 8 }}
+        component: elster-transfer
+    spec:
+      {{- if (.Values.serviceAccount).create }}
+      serviceAccountName: {{ include "app.serviceAccountName" . }}
+      {{- end }}
+      topologySpreadConstraints:
+      - maxSkew: 1
+        topologyKey: kubernetes.io/hostname
+        whenUnsatisfiable: ScheduleAnyway
+        labelSelector:
+          matchLabels:
+            app.kubernetes.io/name: {{ .Release.Name }}
+      containers:
+      - image: "{{ .Values.image.repo }}/{{ .Values.image.name }}:{{ .Values.image.tag }}"
+        env:
+        {{- with include "app.getCustomList" . }}
+{{ . | indent 8 }}
+        {{- end }}
+        imagePullPolicy: Always
+        name: etr
+        ports:
+        - containerPort: 8081
+          name: etr-port
+          protocol: TCP
+        resources:
+        {{- with .Values.resources }}
+{{ toYaml . | indent 10 }}
+        {{- end }}
+        securityContext:
+          allowPrivilegeEscalation: false
+          privileged: false
+          readOnlyRootFilesystem: false
+          runAsNonRoot: true
+          {{- with (.Values.securityContext).runAsUser }}
+          runAsUser: {{ . }}
+          {{- end }}
+          {{- with (.Values.securityContext).runAsGroup }}
+          runAsGroup: {{ . }}
+          {{- end }}
+          {{- with (.Values.securityContext).capabilities }}
+          capabilities:
+{{ toYaml . | indent 12 }}
+          {{- end }}
+        stdin: true
+        terminationMessagePath: /dev/termination-log
+        terminationMessagePolicy: File
+        tty: true
+        volumeMounts:
+          - name: etr-home
+            mountPath: "/home/etr"
+      volumes:
+        - name: etr-home
+          persistentVolumeClaim:
+            claimName: {{ .Release.Name }}-home-pvc
+
+      imagePullSecrets:
+      - name: {{ required "imagePullSecret must be set" .Values.imagePullSecret }}
+      restartPolicy: Always
+      {{- with .Values.podSecurityContext }}
+      securityContext:
+{{ toYaml . | indent 8 }}
+      {{- end }}
+      terminationGracePeriodSeconds: 30
diff --git a/elster-transfer/templates/etr_home_pvc.yaml b/elster-transfer/templates/etr_home_pvc.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4ba0373f65cd070703817f43d2851feaa5c01864
--- /dev/null
+++ b/elster-transfer/templates/etr_home_pvc.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: {{ .Release.Name }}-home-pvc
+  namespace: {{ .Release.Namespace }}
+spec:
+  storageClassName: {{ (.Values.storage).className | default "ssd-retain" }}
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: {{ (.Values.storage).size | default "20Gi" }}
\ No newline at end of file
diff --git a/elster-transfer/templates/network_policy.yaml b/elster-transfer/templates/network_policy.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..35cc5185dbd4edf14cf84ef3c21c679de28e532a
--- /dev/null
+++ b/elster-transfer/templates/network_policy.yaml
@@ -0,0 +1,49 @@
+{{- if not (.Values.networkPolicy).disabled }} 
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: {{ .Release.Name }}-network-policy
+  namespace: {{ .Release.Namespace }}
+spec:
+  podSelector:
+    matchLabels:
+      component: elster-transfer
+  policyTypes:
+    - Ingress
+    - Egress
+  ingress:
+  - ports:
+    - port: 8081
+    from:
+    - namespaceSelector: {}
+      podSelector:
+        matchLabels:
+          component: vorgang-manager
+{{- with (.Values.networkPolicy).additionalIngressConfigLocal }}
+{{ toYaml . | indent 2 }}
+{{- end }}
+{{- with (.Values.networkPolicy).additionalIngressConfigGlobal }}
+{{ toYaml . | indent 2 }}
+{{- end }}
+  egress:
+  - to:
+    - namespaceSelector:
+        matchLabels:
+          kubernetes.io/metadata.name: {{ required "networkPolicy.dnsServerNamespace must be set" (.Values.networkPolicy).dnsServerNamespace }}
+    ports:
+      - port: 53
+        protocol: UDP
+      - port: 53
+        protocol: TCP
+      - port: 5353
+        protocol: UDP
+      - port: 5353
+        protocol: TCP
+{{- with (.Values.networkPolicy).additionalEgressConfigLocal }}
+{{ toYaml . | indent 2 }}
+{{- end }}
+{{- with (.Values.networkPolicy).additionalEgressConfigGlobal }}
+{{ toYaml . | indent 2 }}
+{{- end }}
+
+{{- end }}
\ No newline at end of file
diff --git a/elster-transfer/templates/service.yaml b/elster-transfer/templates/service.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..91d6d0b780158c09e0e4a8baa2fd87e564c6de69
--- /dev/null
+++ b/elster-transfer/templates/service.yaml
@@ -0,0 +1,17 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ .Release.Name }}
+  namespace: {{ .Release.Namespace }}
+  labels:
+    {{- include "app.defaultLabels" . | indent 4 }}
+    component: elster-transfer
+spec:
+  type: ClusterIP
+  ports:
+  - name: etr-port
+    port: 8081
+    protocol: TCP
+  selector:
+    {{- include "app.matchLabels" . | indent 4 }}
+    component: elster-transfer
diff --git a/elster-transfer/templates/service_account.yaml b/elster-transfer/templates/service_account.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e391b60b64fa865ffa5ab9ec4364e060be1fc0ff
--- /dev/null
+++ b/elster-transfer/templates/service_account.yaml
@@ -0,0 +1,7 @@
+{{- if (.Values.serviceAccount).create }}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "app.serviceAccountName" . }}
+  namespace: {{ .Release.Namespace }}
+{{- end }}
\ No newline at end of file
diff --git a/elster-transfer/unit-tests/deployment_basic_spec_test.yaml b/elster-transfer/unit-tests/deployment_basic_spec_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b54c2edc58465b33ad263cdc742883b7f157321f
--- /dev/null
+++ b/elster-transfer/unit-tests/deployment_basic_spec_test.yaml
@@ -0,0 +1,85 @@
+suite: test deployment general values
+release:
+  name: etr
+  namespace: by-helm-test
+templates:
+  - templates/deployment.yaml
+set:
+  imagePullSecret: test-image-pull-secret
+tests:
+  - it: should have apiVersion
+    asserts:
+      - isAPIVersion:
+           of: "apps/v1"
+      - isKind:
+          of: Deployment
+      
+  - it: should have deployment metadata 
+    asserts: 
+      - equal:
+          path: metadata.name
+          value: etr
+      - equal: 
+          path: metadata.namespace
+          value: by-helm-test
+      - equal:
+          path: metadata.labels["app.kubernetes.io/instance"]
+          value: elster-transfer
+      - equal:
+          path: metadata.labels["app.kubernetes.io/managed-by"]
+          value: Helm
+      - equal:
+          path: metadata.labels["app.kubernetes.io/name"]
+          value: etr
+      - equal:
+          path: metadata.labels["app.kubernetes.io/namespace"]
+          value: by-helm-test
+      - equal:
+          path: metadata.labels["app.kubernetes.io/part-of"]
+          value: ozgcloud
+
+  - it: should have deyployment general spec values
+    asserts:
+      - equal:
+          path: spec.progressDeadlineSeconds
+          value: 600
+      - equal:
+          path: spec.replicas
+          value: 1
+      - equal:
+          path: spec.revisionHistoryLimit
+          value: 10
+
+  - it: should have deployment strategy values
+    asserts:
+      - equal: 
+          path: spec.strategy
+          value: 
+            type: Recreate
+
+  - it: should have deployment selector 
+    asserts: 
+      - equal:
+          path: spec.selector.matchLabels["app.kubernetes.io/name"]
+          value: etr
+      - equal: 
+          path: spec.selector.matchLabels["app.kubernetes.io/namespace"]
+          value: by-helm-test
+
+  - it: should have template metadata 
+    asserts: 
+      - equal:
+          path: spec.template.metadata.labels["app.kubernetes.io/name"]
+          value: etr
+      - equal: 
+          path: spec.template.metadata.labels["app.kubernetes.io/namespace"]
+          value: by-helm-test
+      - equal:
+          path: spec.template.metadata.labels.component
+          value: elster-transfer
+
+  - it: should have default image
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[0].image
+          value: docker.ozg-sh.de/etr:24.04.0
\ No newline at end of file
diff --git a/elster-transfer/unit-tests/deployment_container_security_context_test.yaml b/elster-transfer/unit-tests/deployment_container_security_context_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..9c9230e8999f608dc121432d62870be10303eedd
--- /dev/null
+++ b/elster-transfer/unit-tests/deployment_container_security_context_test.yaml
@@ -0,0 +1,64 @@
+suite: test deployment security context
+release:
+  name: etr
+  namespace: by-helm-test
+templates:
+  - templates/deployment.yaml
+set:
+  imagePullSecret: test-image-pull-secret
+tests:
+  - it: check default values
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation
+          value: false
+      - equal:
+          path: spec.template.spec.containers[0].securityContext.privileged
+          value: false
+      - equal:
+          path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem
+          value: false
+      - equal:
+          path: spec.template.spec.containers[0].securityContext.runAsNonRoot
+          value: true
+      - isNull:
+          path: spec.template.spec.containers[0].securityContext.runAsUser
+      - isNull:
+          path: spec.template.spec.containers[0].securityContext.runAsGroup
+      - isNull:
+          path: spec.template.spec.containers[0].securityContext.capabilities
+      - isNull:
+          path: spec.template.spec.securityContext.fsGroup
+  - it: check runAsUser
+    set:
+      securityContext.runAsUser: 1000
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[0].securityContext.runAsUser
+          value: 1000
+  - it: check runAsGroup
+    set:
+      securityContext.runAsGroup: 1000
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[0].securityContext.runAsGroup
+          value: 1000
+  - it: check fsGroup
+    set:
+      podSecurityContext.fsGroup: 1000
+    asserts:
+      - equal:
+          path: spec.template.spec.securityContext.fsGroup
+          value: 1000
+  - it: check capabilities
+    set:
+      securityContext:
+        capabilities:
+          drop:
+            - ALL
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[0].securityContext.capabilities
+          value:
+            drop:
+              - ALL
\ No newline at end of file
diff --git a/elster-transfer/unit-tests/deployment_env_test.yaml b/elster-transfer/unit-tests/deployment_env_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..37a67d1c0e2b38c6e305c4adccd0a71356079352
--- /dev/null
+++ b/elster-transfer/unit-tests/deployment_env_test.yaml
@@ -0,0 +1,48 @@
+suite: test environments
+release:
+  name: etr
+  namespace: by-helm-test
+templates:
+  - templates/deployment.yaml
+set:
+  imagePullSecret: test-image-pull-secret
+tests:
+  - it: check customList as list
+    set:
+      env.customList:
+        - name: my_test_environment_name
+          value: "A test value"
+        - name: test_environment
+          value: "B test value"
+    asserts:
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content:
+            name: my_test_environment_name
+            value: "A test value"
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content:
+            name: test_environment
+            value: "B test value"
+  - it: check customList as dict
+    set:
+      env.customList:
+        my_test_environment_name: "A test value"
+        test_environment: "B test value"
+    asserts:
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content:
+            name: my_test_environment_name
+            value: "A test value"
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content:
+            name: test_environment
+            value: "B test value"
+
+  - it: check env is not set by default
+    asserts:
+      - isNullOrEmpty:
+          path: spec.template.spec.containers[0].env
diff --git a/elster-transfer/unit-tests/deployment_imagepull_secret_test.yaml b/elster-transfer/unit-tests/deployment_imagepull_secret_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..066c007ce5f2a3d6e06a2b609ee11a53548a26f5
--- /dev/null
+++ b/elster-transfer/unit-tests/deployment_imagepull_secret_test.yaml
@@ -0,0 +1,18 @@
+suite: test deployment image pull secret
+release:
+  name: etr
+  namespace: by-helm-test
+templates:
+  - templates/deployment.yaml
+tests:
+  - it: should set the imagePull secret
+    set:
+      imagePullSecret: image-pull-secret
+    asserts:
+      - equal:
+          path: spec.template.spec.imagePullSecrets[0].name
+          value: image-pull-secret
+  - it: should fail if the imagePull secret not set
+    asserts:
+      - failedTemplate:
+            errorMessage: imagePullSecret must be set
\ No newline at end of file
diff --git a/elster-transfer/unit-tests/deployment_resources_test.yaml b/elster-transfer/unit-tests/deployment_resources_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c845a2c7c00cc9adee1b0fa292986bba71efddbb
--- /dev/null
+++ b/elster-transfer/unit-tests/deployment_resources_test.yaml
@@ -0,0 +1,36 @@
+suite: test deployment resources
+release:
+  name: etr
+  namespace: by-helm-test
+templates:
+  - templates/deployment.yaml
+set:
+  imagePullSecret: test-image-pull-secret
+tests:
+  - it: test resources
+    set:
+      resources:
+        limits:
+          cpu: 11m
+          memory: 22Mi
+        requests:
+          cpu: 33m
+          memory: 44Mi
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[0].resources.limits.cpu
+          value: 11m
+      - equal:
+          path: spec.template.spec.containers[0].resources.limits.memory
+          value: 22Mi
+      - equal:
+          path: spec.template.spec.containers[0].resources.requests.cpu
+          value: 33m
+      - equal:
+          path: spec.template.spec.containers[0].resources.requests.memory
+          value: 44Mi
+  - it: no resources defined by default
+    asserts:
+      - isEmpty:
+          path: spec.template.spec.containers[0].resources
+
diff --git a/elster-transfer/unit-tests/deployment_service_account_test.yaml b/elster-transfer/unit-tests/deployment_service_account_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6be510a0c8aac61d8bd6e85c83fe5ddc5c4f9eb6
--- /dev/null
+++ b/elster-transfer/unit-tests/deployment_service_account_test.yaml
@@ -0,0 +1,30 @@
+suite: deployment service account
+release:
+  name: etr
+  namespace: by-helm-test
+templates:
+  - templates/deployment.yaml
+set:
+  imagePullSecret: test-image-pull-secret
+tests:
+  - it: should use service account with default name
+    set:
+      serviceAccount:
+        create: true
+    asserts:
+      - equal:
+          path: spec.template.spec.serviceAccountName
+          value: elster-transfer-service-account
+  - it: should use service account with name
+    set:
+      serviceAccount:
+        create: true
+        name: helm-service-account
+    asserts:
+      - equal:
+          path: spec.template.spec.serviceAccountName
+          value: helm-service-account
+  - it: should use default service account
+    asserts:
+      - isNull:
+          path: spec.template.spec.serviceAccountName
\ No newline at end of file
diff --git a/elster-transfer/unit-tests/deployment_volume_mount_test.yaml b/elster-transfer/unit-tests/deployment_volume_mount_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..82805822b7cc51520716ec8e10b63b0a791d5b09
--- /dev/null
+++ b/elster-transfer/unit-tests/deployment_volume_mount_test.yaml
@@ -0,0 +1,24 @@
+suite: deployment volume mount
+release:
+  name: etr
+  namespace: by-helm-test
+templates:
+  - templates/deployment.yaml
+set:
+  imagePullSecret: test-image-pull-secret
+tests:
+  - it: should have etr-home volume mount
+    asserts:
+      - contains:
+          path: spec.template.spec.containers[0].volumeMounts
+          content:
+            name: etr-home
+            mountPath: "/home/etr"
+  - it: should have etr-home volume
+    asserts:
+      - contains:
+           path: spec.template.spec.volumes
+           content:
+              name: etr-home
+              persistentVolumeClaim:
+                claimName: etr-home-pvc
\ No newline at end of file
diff --git a/elster-transfer/unit-tests/etr_home_pvc_test.yaml b/elster-transfer/unit-tests/etr_home_pvc_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fb125ac7c8f2e7c982d3a5ada57eeb2d731f436a
--- /dev/null
+++ b/elster-transfer/unit-tests/etr_home_pvc_test.yaml
@@ -0,0 +1,57 @@
+suite: test pvc
+release:
+  name: etr
+  namespace: by-helm-test
+templates:
+  - templates/etr_home_pvc.yaml
+tests:
+  - it: should have correct apiVersion
+    asserts:
+      - isAPIVersion:
+          of: v1
+      - isKind:
+          of: PersistentVolumeClaim
+
+  - it: should have metadata
+    asserts:
+      - equal:
+          path: metadata.name
+          value: etr-home-pvc
+      - equal:
+          path: metadata.namespace
+          value: by-helm-test
+
+  - it: should default storageClassName
+    asserts:
+      - equal:
+          path: spec.storageClassName
+          value: ssd-retain
+
+  - it: should set storageClassName
+    set:
+      storage.className: ssd-delete
+    asserts:
+      - equal:
+          path: spec.storageClassName
+          value: ssd-delete
+
+  - it: should have accessMode
+    asserts:
+      - equal:
+          path: spec.accessModes
+          value:
+            - ReadWriteOnce
+
+  - it: should have default storage size
+    asserts:
+      - equal:
+          path: spec.resources.requests.storage
+          value: 20Gi
+
+  - it: should set storage size
+    set:
+      storage.size: 10Gi
+    asserts:
+      - equal:
+          path: spec.resources.requests.storage
+          value: 10Gi
diff --git a/elster-transfer/unit-tests/network_policy_test.yaml b/elster-transfer/unit-tests/network_policy_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c894864df61555eea45cb4080829ba8602fb7ad5
--- /dev/null
+++ b/elster-transfer/unit-tests/network_policy_test.yaml
@@ -0,0 +1,194 @@
+suite: network policy test
+release:
+  name: etr
+  namespace: by-helm-test
+templates:
+  - templates/network_policy.yaml
+tests:
+  - it: should match apiVersion
+    set:
+      networkPolicy:
+        dnsServerNamespace: test-dns-namespace
+    asserts:
+      - isAPIVersion:
+          of: networking.k8s.io/v1
+      - isKind:
+          of: NetworkPolicy
+
+  - it: validate metadata
+    set:
+      networkPolicy:
+        dnsServerNamespace: test-dns-namespace
+    asserts:
+      - equal:
+          path: metadata
+          value:
+            name: etr-network-policy
+            namespace: by-helm-test
+
+  - it: validate podSelector
+    set:
+      networkPolicy:
+        dnsServerNamespace: test-dns-namespace
+    asserts:
+      - equal:
+          path: spec.podSelector
+          value:
+              matchLabels:
+                component: elster-transfer
+
+  - it: validate policyTypes
+    set:
+      networkPolicy:
+        dnsServerNamespace: test-dns-namespace
+    asserts:
+      - equal:
+          path: spec.policyTypes
+          value:
+            - Ingress
+            - Egress
+
+  - it: should add ingress rule for vorgang-manager
+    set:
+      networkPolicy:
+        dnsServerNamespace: test-dns-namespace
+    asserts:
+      - equal:
+          path: spec.ingress
+          value:
+            - ports:
+              - port: 8081
+              from:
+              - namespaceSelector: {}
+                podSelector:
+                  matchLabels:
+                    component: vorgang-manager
+
+  - it: should add egress rule to dns service
+    set:
+      networkPolicy:
+        dnsServerNamespace: test-dns-namespace
+    asserts:
+      - equal:
+          path: spec.egress
+          value:
+            - to:
+              - namespaceSelector:
+                  matchLabels:
+                    kubernetes.io/metadata.name: test-dns-namespace
+              ports:
+                - port: 53
+                  protocol: UDP
+                - port: 53
+                  protocol: TCP
+                - port: 5353
+                  protocol: UDP
+                - port: 5353
+                  protocol: TCP
+
+  - it: test network policy disabled
+    set:
+      networkPolicy:
+        disabled: true
+        dnsServerNamespace: test-dns-namespace
+    asserts:
+      - hasDocuments:
+          count: 0
+
+  - it: test network policy unset should be disabled
+    set:
+      networkPolicy:
+        disabled: false
+        dnsServerNamespace: test-dns-namespace
+    asserts:
+      - hasDocuments:
+          count: 1
+
+
+  - it: add ingress rule local by values
+    set:
+      networkPolicy:
+        dnsServerNamespace: test-dns-namespace
+        additionalIngressConfigGlobal:
+        - from:
+          - podSelector: 
+              matchLabels:
+                component: client2
+    asserts:
+      - contains:
+          path: spec.ingress
+          content:
+            from:
+            - podSelector: 
+                matchLabels:
+                  component: client2
+  - it: add ingress rule global by values
+    set:
+      networkPolicy:
+        dnsServerNamespace: test-dns-namespace
+        additionalIngressConfigLocal:
+        - from:
+          - podSelector: 
+              matchLabels:
+                component: client2
+    asserts:
+      - contains:
+          path: spec.ingress
+          content:
+            from:
+            - podSelector: 
+                matchLabels:
+                  component: client2
+
+  - it: add egress rules local by values
+    set:
+      networkPolicy:
+        dnsServerNamespace: test-dns-namespace
+        additionalEgressConfigGlobal:
+        - to:
+          - ipBlock:
+              cidr: 1.2.3.4/32
+        - to:
+          - podSelector:
+              matchLabels:
+                component: ozg-testservice
+          ports:
+            - port: 12345
+              protocol: TCP
+    asserts:
+    - contains:
+        path: spec.egress
+        content:
+          to:
+          - ipBlock:
+              cidr: 1.2.3.4/32
+    - contains:
+        path: spec.egress
+        content:
+          to:
+          - podSelector:
+              matchLabels:
+                component: ozg-testservice
+          ports:
+            - port: 12345
+              protocol: TCP
+  - it: add egress rules global by values
+    set:
+      networkPolicy:
+        dnsServerNamespace: test-dns-namespace
+        additionalEgressConfigLocal:
+        - to:
+          - ipBlock:
+              cidr: 1.2.3.4/32
+        - to:
+          - podSelector:
+              matchLabels:
+                additionalEgressConfigLocal: yes
+    asserts:
+    - contains:
+        path: spec.egress
+        content:
+          to:
+          - podSelector:
+              matchLabels:
+                additionalEgressConfigLocal: yes
\ No newline at end of file
diff --git a/elster-transfer/unit-tests/service_account_test.yaml b/elster-transfer/unit-tests/service_account_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..cad4d5e0d5ee31854d7a393152c6f3a2c2f697bf
--- /dev/null
+++ b/elster-transfer/unit-tests/service_account_test.yaml
@@ -0,0 +1,40 @@
+suite: test service account
+release:
+  name: etr
+  namespace: by-helm-test
+templates:
+  - templates/service_account.yaml
+tests:
+  - it: should create service account with default name
+    set:
+      serviceAccount:
+        create: true
+    asserts:
+      - isKind:
+          of: ServiceAccount
+      - isAPIVersion:
+          of: v1
+      - equal:
+          path: metadata.name
+          value: elster-transfer-service-account
+      - equal:
+          path: metadata.namespace
+          value: by-helm-test
+  - it: should create service account with name
+    set:
+      serviceAccount:
+        create: true
+        name: helm-service-account
+    asserts:
+      - isKind:
+          of: ServiceAccount
+      - equal:
+          path: metadata.name
+          value: helm-service-account
+      - equal:
+          path: metadata.namespace
+          value: by-helm-test
+  - it: should not create service account
+    asserts:
+      - hasDocuments:
+        count: 0
\ No newline at end of file
diff --git a/elster-transfer/unit-tests/service_test.yaml b/elster-transfer/unit-tests/service_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..236b245f3a1b0bca442a81d7f6ccb645366ada30
--- /dev/null
+++ b/elster-transfer/unit-tests/service_test.yaml
@@ -0,0 +1,69 @@
+suite: test service
+release:
+  name: etr
+  namespace: by-helm-test
+templates:
+  - templates/service.yaml
+tests:
+  - it: should have correct apiVersion
+    asserts:
+      - isKind:
+          of: Service
+      - isAPIVersion:
+          of: v1
+
+  - it: should have metadata
+    asserts:
+      - equal:
+          path: metadata.name
+          value: etr
+      - equal:
+          path: metadata.namespace
+          value: by-helm-test
+      - equal:
+          path: metadata.labels["component"]
+          value: elster-transfer
+      - equal:
+          path: metadata.labels["app.kubernetes.io/instance"]
+          value: elster-transfer
+      - equal:
+          path: metadata.labels["app.kubernetes.io/managed-by"]
+          value: Helm
+      - equal:
+          path: metadata.labels["app.kubernetes.io/name"]
+          value: etr
+      - equal:
+          path: metadata.labels["app.kubernetes.io/namespace"]
+          value: by-helm-test
+      - equal:
+          path: metadata.labels["app.kubernetes.io/part-of"]
+          value: ozgcloud
+
+  - it: should be of type ClusterIP
+    asserts:
+      - equal:
+          path: spec.type
+          value: ClusterIP
+
+  - it: ports should contain the etr port
+    asserts:
+      - contains:
+          path: spec.ports
+          content:
+            name: etr-port
+            port: 8081
+            protocol: TCP
+          count: 1
+          any: true
+
+  - it: should have selector
+    asserts:
+      - equal:
+          path: spec.selector["component"]
+          value: elster-transfer
+      - equal:
+          path: spec.selector["app.kubernetes.io/name"]
+          value: etr
+      - equal:
+          path: spec.selector["app.kubernetes.io/namespace"]
+          value: by-helm-test
diff --git a/elster-transfer/values.yaml b/elster-transfer/values.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4cf57a21745aa2181e6a00c7b3e555a79f6d4452
--- /dev/null
+++ b/elster-transfer/values.yaml
@@ -0,0 +1,6 @@
+replicaCount: 1
+
+image:
+  repo: docker.ozg-sh.de
+  name: etr
+  tag: "24.04.0"
\ No newline at end of file
diff --git a/run_helm_test.sh b/run_helm_test.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f62dcabe3f432a2620fe57cde5242b2ccffaff4f
--- /dev/null
+++ b/run_helm_test.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+set -e
+set -x
+
+helm template  ./elster-transfer -f elster-transfer/lint-values.yaml 
+
+helm lint -f elster-transfer/lint-values.yaml  ./elster-transfer
+helm unittest -f "unit-tests/**/*.yaml" ./elster-transfer
\ No newline at end of file