diff --git a/swh/templates/storage/configmap.yaml b/swh/templates/storage/configmap.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..41c41b577b549b9276017cbf639545e48262fabf
--- /dev/null
+++ b/swh/templates/storage/configmap.yaml
@@ -0,0 +1,25 @@
+{{ if .Values.storage.enabled -}}
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  namespace: {{ .Values.namespace }}
+  name: storage-configuration-template
+data:
+  config.yml.template: |
+    storage:
+      cls: {{ .Values.storage.storageClass }}
+      hosts:
+      {{- range $seed := .Values.storage.cassandra.seeds }}
+        - {{ $seed }}
+      {{- end }}
+      keyspace: {{ .Values.storage.cassandra.keySpace }}
+      consistency_level: {{ .Values.storage.cassandra.consistencyLevel }}
+      {{- if .Values.storage.specific_options -}}
+        {{- range $option, $value := .Values.storage.specific_options }}
+      {{ $option }}: {{ $value }}
+        {{- end }}
+      {{- end }}
+      objstorage:
+        cls: noop
+{{- end -}}
diff --git a/swh/templates/storage/deployment.yaml b/swh/templates/storage/deployment.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..2fb05c9164bb0549bb112f359283eef56909487e
--- /dev/null
+++ b/swh/templates/storage/deployment.yaml
@@ -0,0 +1,127 @@
+{{ if .Values.storage.enabled -}}
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  namespace: {{ .Values.namespace }}
+  name: storage
+  labels:
+    app: storage
+spec:
+  revisionHistoryLimit: 2
+  replicas: {{ .Values.storage.replicas | default 1 }}
+  selector:
+    matchLabels:
+      app: storage
+  strategy:
+    type: RollingUpdate
+    rollingUpdate:
+      maxSurge: 1
+  template:
+    metadata:
+      labels:
+        app: storage
+      annotations:
+        checksum/config: {{ include (print $.Template.BasePath "/storage/configmap.yaml") . | sha256sum }}
+    spec:
+      {{- if .Values.storage.affinity }}
+      affinity:
+        {{- toYaml .Values.storage.affinity | nindent 8 }}
+      {{- end }}
+      initContainers:
+        - name: prepare-configuration
+          image: debian:bullseye
+          imagePullPolicy: Always
+          command:
+          - /bin/bash
+          args:
+          - -c
+          - eval echo "\"$(</etc/swh/configuration-template/config.yml.template)\"" > /etc/swh/config.yml
+          volumeMounts:
+          - name: configuration
+            mountPath: /etc/swh
+          - name: configuration-template
+            mountPath: /etc/swh/configuration-template
+        {{- if .Values.storage.cassandra.initKeyspace }}
+        - name: init-database
+          image: {{ .Values.swh_storage_image }}:{{ .Values.swh_storage_image_version }}
+          imagePullPolicy: Always
+          command:
+          - /bin/bash
+          args:
+          - -c
+          - eval "echo \"from swh.storage.cassandra import create_keyspace; create_keyspace(['{{ first .Values.storage.cassandra.seeds }}'], 'swh')\" | python3"
+        {{- end }}
+      containers:
+        - name: storage
+          resources:
+            requests:
+              memory: {{ .Values.storage.requestedMemory | default "512Mi" }}
+              cpu: {{ .Values.storage.requestedCpu | default "500m" }}
+          image: {{ .Values.swh_storage_image }}:{{ .Values.swh_storage_image_version }}
+          imagePullPolicy: Always
+          ports:
+            - containerPort: 5002
+              name: rpc
+          readinessProbe:
+            httpGet:
+              path: /
+              port: rpc
+            initialDelaySeconds: 15
+            failureThreshold: 30
+            periodSeconds: 5
+          livenessProbe:
+            httpGet:
+              path: /
+              port: rpc
+            initialDelaySeconds: 10
+            periodSeconds: 5          
+          command:
+          - /bin/bash
+          args:
+          - -c
+          - /opt/swh/entrypoint.sh
+          env:
+            {{ if .Values.storage.gunicorn -}}
+            - name: THREADS
+              value: {{ .Values.storage.gunicorn.threads | default 5 | quote }}
+            - name: WORKERS
+              value: {{ .Values.storage.gunicorn.workers | default 2 | quote }}
+            - name: TIMEOUT
+              value: {{ .Values.storage.gunicorn.timeout | default 60 | quote }}
+            {{ end -}}
+            - name: STATSD_HOST
+              value: {{ .Values.statsdExternalHost | default "prometheus-statsd-exporter" }}
+            - name: STATSD_PORT
+              value: {{ .Values.statsdPort | default "9125" | quote }}
+            - name: LOG_LEVEL
+              value: {{ .Values.storage.logLevel | quote }}              
+          {{- if .Values.storage.sentry.enabled }}
+            - name: SWH_SENTRY_ENVIRONMENT
+              value: {{ .Values.sentry.environment }}
+            - name: SWH_MAIN_PACKAGE
+              value: swh.storage
+            - name: SWH_SENTRY_DSN
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Values.storage.sentry.secretKeyRef }}
+                  key: {{ .Values.storage.sentry.secretKeyName }}
+                  # 'name' secret should exist & include key
+                  # if the setting doesn't exist, sentry pushes will be disabled
+                  optional: true
+            - name: SWH_SENTRY_DISABLE_LOGGING_EVENTS
+              value: "true"
+          {{- end }}
+          volumeMounts:
+          - name: configuration
+            mountPath: /etc/swh
+      volumes:
+      - name: configuration
+        emptyDir: {}
+      - name: configuration-template
+        configMap:
+          name: storage-configuration-template
+          items:
+          - key: "config.yml.template"
+            path: "config.yml.template"
+{{- end -}}
diff --git a/swh/templates/storage/service.yaml b/swh/templates/storage/service.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c11cacbbd1e71a265a636a27df2f11385016f98f
--- /dev/null
+++ b/swh/templates/storage/service.yaml
@@ -0,0 +1,15 @@
+{{ if .Values.storage.enabled -}}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: storage
+  namespace: {{ .Values.namespace }}
+spec:
+  type: ClusterIP
+  selector:
+    app: storage
+  ports:
+    - port: 5002
+      targetPort: 5002
+{{ end }}
diff --git a/swh/tests/storage_confimap_test.yaml b/swh/tests/storage_confimap_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..7e13891397870c6fc80a6b9f2835ff64d6d517ce
--- /dev/null
+++ b/swh/tests/storage_confimap_test.yaml
@@ -0,0 +1,28 @@
+suite: test storage configmap deployment
+templates:
+  - storage/configmap.yaml
+tests:
+  - it: storage configmap is deployed when activated
+    set:
+      storage.enabled: true
+    asserts:
+        - containsDocument:
+            kind: ConfigMap
+            apiVersion: v1
+        - equal:
+            path: metadata.namespace
+            value: swh
+        # not testable as there is a dot in the entry name
+        # - contains:
+        #     path: data.config.yml
+        #     content:
+        #       debug: false
+        #     count: 1
+  - it: storage namespace
+    set:
+      storage.enabled: true
+      namespace: mynamespace
+    asserts:
+        - equal:
+            path: metadata.namespace
+            value: mynamespace
\ No newline at end of file
diff --git a/swh/tests/storage_deployment_test.yaml b/swh/tests/storage_deployment_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..798fa75def56d1e1ed00da0161dede8e3aa93105
--- /dev/null
+++ b/swh/tests/storage_deployment_test.yaml
@@ -0,0 +1,134 @@
+suite: test storage deployment
+templates:
+  - storage/configmap.yaml
+  - storage/deployment.yaml
+tests:
+  - it: Storage is deployed 
+    templates:
+      - storage/deployment.yaml
+    set:
+      storage.enabled: true
+      swh_storage_image: image
+      swh_storage_image_version: version
+      storage.requestedMemory: memory
+      storage.requestedCpu: cpu
+    asserts:
+      - hasDocuments:
+          count: 1
+      - equal:
+          path: metadata.namespace
+          value: swh
+      - equal:
+          path: spec.replicas
+          value: 1
+      - equal:
+          path: spec.template.spec.containers[0].resources.requests.memory
+          value: memory 
+      - equal:
+          path: spec.template.spec.containers[0].resources.requests.cpu
+          value: cpu
+      - equal:
+          path: spec.template.spec.containers[0].image
+          value: image:version
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "LOG_LEVEL")].value
+          value: "INFO"
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "STATSD_HOST")].value
+          value: prometheus-statsd-exporter
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "STATSD_PORT")].value
+          value: "9125"
+      - isNull:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "THREADS")]
+      - isNull:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "WORKERS")]
+      - isNull:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "TIMEOUT")]
+      - isNull:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "SWH_SENTRY_ENVIRONMENT")]
+      - isNull:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "SWH_MAIN_PACKAGE")]
+      - isNull:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "SWH_SENTRY_DSN")]
+  - it: Storage gunicorn workers configuration
+    templates:
+      - storage/deployment.yaml
+    set:
+      storage.enabled: true
+      swh_storage_image: image
+      swh_storage_image_version: version
+      storage.gunicorn.threads: 101
+      storage.gunicorn.workers: 102
+      storage.gunicorn.timeout: 103
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "THREADS")].value
+          value: "101"
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "WORKERS")].value
+          value: "102"
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "TIMEOUT")].value
+          value: "103"
+  - it: Storage statsd overridden configuration
+    templates:
+      - storage/deployment.yaml
+    set:
+      storage.enabled: true
+      swh_storage_image: image
+      swh_storage_image_version: version
+      statsdExternalHost: my-statsd-host
+      statsdPort: 9999
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "STATSD_HOST")].value
+          value: my-statsd-host
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "STATSD_PORT")].value
+          value: "9999"
+  - it: Storage sentry default configuration
+    templates:
+      - storage/deployment.yaml
+    set:
+      storage.enabled: true
+      storage.sentry.enabled: true
+      swh_storage_image: image
+      swh_storage_image_version: version
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "SWH_SENTRY_ENVIRONMENT")].value
+          value: production
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "SWH_MAIN_PACKAGE")].value
+          value: swh.storage
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "SWH_SENTRY_DSN")].valueFrom.secretKeyRef.name
+          value: common-secrets
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "SWH_SENTRY_DSN")].valueFrom.secretKeyRef.key
+          value: storage-sentry-dsn
+  - it: Storage sentry overridden configuration
+    templates:
+      - storage/deployment.yaml
+    set:
+      sentry.environment: my-environment
+      storage.enabled: true
+      storage.sentry.enabled: true
+      storage.sentry.secretKeyRef: my-secret
+      storage.sentry.secretKeyName: my-key
+      swh_storage_image: image
+      swh_storage_image_version: version
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "SWH_SENTRY_ENVIRONMENT")].value
+          value: my-environment
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "SWH_MAIN_PACKAGE")].value
+          value: swh.storage
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "SWH_SENTRY_DSN")].valueFrom.secretKeyRef.name
+          value: my-secret
+      - equal:
+          path: spec.template.spec.containers[?(@.name == "storage")].env[?(@.name == "SWH_SENTRY_DSN")].valueFrom.secretKeyRef.key
+          value: my-key
\ No newline at end of file
diff --git a/swh/tests/storage_global_test.yaml b/swh/tests/storage_global_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..39c94140e0ef0694327d06ccfe6bec3bb36a055d
--- /dev/null
+++ b/swh/tests/storage_global_test.yaml
@@ -0,0 +1,12 @@
+suite: test storage deployment
+templates:
+  - storage/deployment.yaml
+  - storage/configmap.yaml
+  - storage/service.yaml
+tests:
+  - it: Storage is not deployed by default
+    values:
+      - ../values.yaml
+    asserts:
+      - hasDocuments:
+          count: 0
\ No newline at end of file
diff --git a/swh/tests/storage_service_test.yaml b/swh/tests/storage_service_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a3e0bef01b151f202509b77d45524a4293450d9b
--- /dev/null
+++ b/swh/tests/storage_service_test.yaml
@@ -0,0 +1,15 @@
+suite: test swh-storage service deployment
+templates:
+  - storage/service.yaml
+tests:
+  - it: swh-storage service is deployed when activated
+    set:
+      storage.enabled: true
+      namespace: mynamespace
+    asserts:
+        - containsDocument:
+            kind: Service
+            apiVersion: v1
+        - equal:
+            path: metadata.namespace
+            value: mynamespace
\ No newline at end of file
diff --git a/swh/values.yaml b/swh/values.yaml
index f4c88dcc4d175f5a2f17a10d3409a7dda4a4f63b..f2d1e52cfb4b0894aa527e58f4e1a17ab3fce61b 100644
--- a/swh/values.yaml
+++ b/swh/values.yaml
@@ -160,13 +160,49 @@ indexers:
 listers:
   enabled: false
 
+storage:
+  enabled: false
+  logLevel: INFO
+  replicas: 1
+  requestedCpu: 50m
+  requestedMemory: 100Mi
+  autoScaling:
+    maxReplicaCount: 1
+  storageClass: cassandra
+#  affinity:
+#    nodeAffinity:
+#      requiredDuringSchedulingIgnoredDuringExecution:
+#        nodeSelectorTerms:
+#        - matchExpressions:
+#          - key: "swh/storage"
+#            operator: In
+#            values:
+#            - "true"
+#  gunicorn:
+#    threads: 5
+#    workers: 2
+#    timeout: 60
+  sentry:
+    enabled: false
+    # name of the secret containing the $secretKeyName value
+    # it defines the sentry token, host and projet to access
+    # like https://token@sentry.host/id
+    secretKeyRef: common-secrets
+    secretKeyName: storage-sentry-dsn
+  cassandra:
+    initKeyspace: false # only to bootstrap a new cassandra database
+    seeds:
+      - seed1
+    keySpace: swh
+    consistencyLevel: LOCAL_QUORUM
+
 statsd_exporter:
   enabled: false
   image: prom/statsd-exporter
   imageVersion: "v0.22.7"
 
 memcached:
-  # Deploy a memcached instance used by the webapp and graphql for sessions caching
+  # Deploy a memcached instance used by the webapp and storage for sessions caching
   enabled: false
   image: memcached:1.6.18
   memory: 256m
@@ -176,3 +212,4 @@ memcached:
     # Activate the deployment of the memcached exporter and ServiceMonitor
     enabled: true
     image: quay.io/prometheus/memcached-exporter:v0.11.1
+