Repository: CS-SI/eodag
Pull request #739 (2023-06-19 11:21:37)

feat: helm chart
This pull request introduce a Helm chart to easily deploy EODAG server on Kubernetes

Fix https://github.com/CS-SI/eodag/issues/662


Patch:
diff --git a/.gitignore b/.gitignore
index 6c8d9419..0e6aad37 100644
--- a/.gitignore
+++ b/.gitignore
@@ -116,3 +116,6 @@ ext_product_types.json
 .project
 .pydevproject
 .settings/
+
+# Helm chart dependencies
+charts/**/*.tgz
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index bb0201ac..54e5e823 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -11,6 +11,7 @@ repos:
     -   id: check-json
     -   id: check-yaml
         args: [--allow-multiple-documents, --unsafe]
+        exclude: ^charts/eodag-server/templates/
     -   id: check-xml
     -   id: check-added-large-files
         args: ['--maxkb=1600']
diff --git a/charts/eodag-server/Chart.lock b/charts/eodag-server/Chart.lock
new file mode 100644
index 00000000..d8f06a13
--- /dev/null
+++ b/charts/eodag-server/Chart.lock
@@ -0,0 +1,6 @@
+dependencies:
+- name: common
+  repository: oci://registry-1.docker.io/bitnamicharts
+  version: 2.4.0
+digest: sha256:b371e6f7f1449fa3abdcb97a04b0bbb2b5d36a4facb8e79041ac36a455b02bb0
+generated: "2023-06-19T12:39:44.271254606+02:00"
diff --git a/charts/eodag-server/Chart.yaml b/charts/eodag-server/Chart.yaml
new file mode 100644
index 00000000..4333ee6b
--- /dev/null
+++ b/charts/eodag-server/Chart.yaml
@@ -0,0 +1,18 @@
+apiVersion: v2
+appVersion: 2.10.0
+dependencies:
+- name: common
+  repository:  oci://registry-1.docker.io/bitnamicharts
+  tags:
+  - bitnami-common
+  version: 2.x
+description: EODAG (Earth Observation Data Access Gateway) is a tool for searching,
+  aggregating results and downloading remote sensed images offering a unified API
+  for data access regardless of the data provider.
+home: https://github.com/CS-SI/eodag
+icon: https://raw.githubusercontent.com/CS-SI/eodag/master/docs/_static/eodag_bycs.png
+name: eodag-server
+sources:
+- https://github.com/CS-SI/eodag
+type: application
+version: 2.10.0
diff --git a/charts/eodag-server/README.md b/charts/eodag-server/README.md
new file mode 100644
index 00000000..646193d2
--- /dev/null
+++ b/charts/eodag-server/README.md
@@ -0,0 +1,206 @@
+# EODAG Server
+
+This chart bootstraps a [EODAG Server](https://github.com/CS-SI/eodag) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
+
+## TL;DR
+
+```console
+helm install my-release oci://registry-1.docker.io/csspace/eodag-server
+```
+
+## Prerequisites
+
+- Kubernetes 1.19+
+- Helm 3.2.0+
+
+## Installing the Chart
+
+To install the chart with the release name `my-release`:
+
+```console
+helm install my-release oci://registry-1.docker.io/csspace/eodag-server
+```
+
+These commands deploy EODAG Server on the Kubernetes cluster in the default configuration.
+
+> **Tip**: List all releases using `helm list`
+
+## Uninstalling the Chart
+
+
+To uninstall the `my-release` deployment:
+
+```bash
+helm uninstall my-release
+```
+
+The command removes all the Kubernetes components associated with the chart and deletes the release.
+
+## Parameters
+
+### Global parameters
+
+| Name                      | Description                                     | Value |
+| ------------------------- | ----------------------------------------------- | ----- |
+| `global.imageRegistry`    | Global Docker image registry                    | `""`  |
+| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]`  |
+| `global.storageClass`     | Global StorageClass for Persistent Volume(s)    | `""`  |
+
+
+### Common parameters
+
+| Name                | Description                                                          | Value           |
+| ------------------- | -------------------------------------------------------------------- | --------------- |
+| `kubeVersion`       | Force target Kubernetes version (using Helm capabilities if not set) | `""`            |
+| `nameOverride`      | String to partially override common.names.fullname                   | `""`            |
+| `fullnameOverride`  | String to fully override common.names.fullname                       | `""`            |
+| `namespaceOverride` | String to fully override common.names.namespaceapi                   | `""`            |
+| `commonLabels`      | Labels to add to all deployed objects                                | `{}`            |
+| `commonAnnotations` | Annotations to add to all deployed objects                           | `{}`            |
+| `clusterDomain`     | Kubernetes cluster domain name                                       | `cluster.local` |
+| `extraDeploy`       | Array of extra objects to deploy with the release                    | `[]`            |
+
+
+### EODAG Server parameters
+
+| Name                                                | Description                                                                                                               | Value                    |
+| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
+| `image.registry`                                    | EODAG Server image registry                                                                                               | `docker.io`              |
+| `image.repository`                                  | EODAG Server image repository                                                                                             | `csspace/eodag-server`   |
+| `image.tag`                                         | Overrides the EODAG Server image tag whose default is the chart appVersion (immutable tags are recommended)               | `""`                     |
+| `image.digest`                                      | EODAG Server image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag              | `""`                     |
+| `image.pullPolicy`                                  | EODAG Server image pull policy                                                                                            | `IfNotPresent`           |
+| `image.pullSecrets`                                 | Specify docker-registry secret names as an array                                                                          | `[]`                     |
+| `replicaCount`                                      | Number of EODAG Server replicas                                                                                           | `1`                      |
+| `startupProbe.enabled`                              | Enable startupProbe on EODAG Server containers                                                                            | `false`                  |
+| `startupProbe.initialDelaySeconds`                  | Initial delay seconds for startupProbe                                                                                    | `10`                     |
+| `startupProbe.periodSeconds`                        | Period seconds for startupProbe                                                                                           | `10`                     |
+| `startupProbe.timeoutSeconds`                       | Timeout seconds for startupProbe                                                                                          | `1`                      |
+| `startupProbe.failureThreshold`                     | Failure threshold for startupProbe                                                                                        | `3`                      |
+| `startupProbe.successThreshold`                     | Success threshold for startupProbe                                                                                        | `1`                      |
+| `livenessProbe.enabled`                             | Enable livenessProbe on EODAG Server containers                                                                           | `false`                  |
+| `livenessProbe.initialDelaySeconds`                 | Initial delay seconds for livenessProbe                                                                                   | `3`                      |
+| `livenessProbe.periodSeconds`                       | Period seconds for livenessProbe                                                                                          | `10`                     |
+| `livenessProbe.timeoutSeconds`                      | Timeout seconds for livenessProbe                                                                                         | `1`                      |
+| `livenessProbe.failureThreshold`                    | Failure threshold for livenessProbe                                                                                       | `3`                      |
+| `livenessProbe.successThreshold`                    | Success threshold for livenessProbe                                                                                       | `1`                      |
+| `readinessProbe.enabled`                            | Enable readinessProbe on EODAG Server containers                                                                          | `false`                  |
+| `readinessProbe.initialDelaySeconds`                | Initial delay seconds for readinessProbe                                                                                  | `3`                      |
+| `readinessProbe.periodSeconds`                      | Period seconds for readinessProbe                                                                                         | `10`                     |
+| `readinessProbe.timeoutSeconds`                     | Timeout seconds for readinessProbe                                                                                        | `1`                      |
+| `readinessProbe.failureThreshold`                   | Failure threshold for readinessProbe                                                                                      | `3`                      |
+| `readinessProbe.successThreshold`                   | Success threshold for readinessProbe                                                                                      | `1`                      |
+| `customLivenessProbe`                               | Custom livenessProbe that overrides the default one                                                                       | `{}`                     |
+| `customReadinessProbe`                              | Custom readinessProbe that overrides the default one                                                                      | `{}`                     |
+| `resources.limits`                                  | The resources limits for the EODAG Server containers                                                                      | `{}`                     |
+| `resources.requests`                                | The requested resources for the EODAG Server containers                                                                   | `{}`                     |
+| `podSecurityContext.enabled`                        | Enabled EODAG Server pods' Security Context                                                                               | `false`                  |
+| `podSecurityContext.fsGroup`                        | Set EODAG Server pod's Security Context fsGroup                                                                           | `1001`                   |
+| `containerSecurityContext.enabled`                  | Enabled EODAG Server containers' Security Context                                                                         | `false`                  |
+| `containerSecurityContext.runAsUser`                | Set EODAG Server containers' Security Context runAsUser                                                                   | `1001`                   |
+| `containerSecurityContext.allowPrivilegeEscalation` | Set EODAG Server containers' Security Context allowPrivilegeEscalation                                                    | `false`                  |
+| `containerSecurityContext.capabilities.drop`        | Set EODAG Server containers' Security Context capabilities to be dropped                                                  | `["all"]`                |
+| `containerSecurityContext.readOnlyRootFilesystem`   | Set EODAG Server containers' Security Context readOnlyRootFilesystem                                                      | `false`                  |
+| `containerSecurityContext.runAsNonRoot`             | Set EODAG Server container's Security Context runAsNonRoot                                                                | `true`                   |
+| `command`                                           | Override default container command (useful when using custom images)                                                      | `[]`                     |
+| `args`                                              | Override default container args (useful when using custom images). Overrides the defaultArgs.                             | `[]`                     |
+| `containerPorts.http`                               | EODAG Server application HTTP port number                                                                                 | `5000`                   |
+| `persistence.enabled`                               | Enable persistence using PVC                                                                                              | `false`                  |
+| `persistence.medium`                                | Provide a medium for `emptyDir` volumes.                                                                                  | `""`                     |
+| `persistence.sizeLimit`                             | Set this to enable a size limit for `emptyDir` volumes.                                                                   | `8Gi`                    |
+| `persistence.storageClass`                          | PVC Storage Class for Matomo volume                                                                                       | `""`                     |
+| `persistence.accessModes`                           | PVC Access Mode for Matomo volume                                                                                         | `["ReadWriteOnce"]`      |
+| `persistence.size`                                  | PVC Storage Request for Matomo volume                                                                                     | `8Gi`                    |
+| `persistence.dataSource`                            | Custom PVC data source                                                                                                    | `{}`                     |
+| `persistence.existingClaim`                         | A manually managed Persistent Volume Claim                                                                                | `""`                     |
+| `persistence.hostPath`                              | If defined, the matomo-data volume will mount to the specified hostPath.                                                  | `""`                     |
+| `persistence.annotations`                           | Persistent Volume Claim annotations                                                                                       | `{}`                     |
+| `persistence.labels`                                | Additional custom labels for the PVC                                                                                      | `{}`                     |
+| `persistence.selector`                              | Selector to match an existing Persistent Volume for Matomo data PVC                                                       | `{}`                     |
+| `hostAliases`                                       | EODAG Server pods host aliases                                                                                            | `[]`                     |
+| `podLabels`                                         | Extra labels for EODAG Server pods                                                                                        | `{}`                     |
+| `podAnnotations`                                    | Annotations for EODAG Server pods                                                                                         | `{}`                     |
+| `podAffinityPreset`                                 | Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard`                                       | `""`                     |
+| `podAntiAffinityPreset`                             | Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard`                                  | `soft`                   |
+| `nodeAffinityPreset.type`                           | Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard`                                 | `""`                     |
+| `nodeAffinityPreset.key`                            | Node label key to match. Ignored if `affinity` is set                                                                     | `""`                     |
+| `nodeAffinityPreset.values`                         | Node label values to match. Ignored if `affinity` is set                                                                  | `[]`                     |
+| `affinity`                                          | Affinity for EODAG Server pods assignment                                                                                 | `{}`                     |
+| `nodeSelector`                                      | Node labels for EODAG Server pods assignment                                                                              | `{}`                     |
+| `tolerations`                                       | Tolerations for EODAG Server pods assignment                                                                              | `[]`                     |
+| `schedulerName`                                     | Name of the k8s scheduler (other than default)                                                                            | `""`                     |
+| `shareProcessNamespace`                             | Enable shared process namespace in a pod.                                                                                 | `false`                  |
+| `topologySpreadConstraints`                         | Topology Spread Constraints for pod assignment                                                                            | `[]`                     |
+| `updateStrategy.type`                               | EODAG Server statefulset strategy type                                                                                    | `RollingUpdate`          |
+| `priorityClassName`                                 | EODAG Server pods' priorityClassName                                                                                      | `""`                     |
+| `runtimeClassName`                                  | Name of the runtime class to be used by pod(s)                                                                            | `""`                     |
+| `lifecycleHooks`                                    | for the EODAG Server container(s) to automate configuration before or after startup                                       | `{}`                     |
+| `extraEnvVars`                                      | Array with extra environment variables to add to EODAG Server nodes                                                       | `[]`                     |
+| `extraEnvVarsCM`                                    | Name of existing ConfigMap containing extra env vars for EODAG Server nodes                                               | `""`                     |
+| `extraEnvVarsSecret`                                | Name of existing Secret containing extra env vars for EODAG Server nodes                                                  | `""`                     |
+| `extraVolumes`                                      | Optionally specify extra list of additional volumes for the EODAG Server pod(s)                                           | `[]`                     |
+| `extraVolumeMounts`                                 | Optionally specify extra list of additional volumeMounts for the EODAG Server container(s)                                | `[]`                     |
+| `sidecars`                                          | Add additional sidecar containers to the EODAG Server pod(s)                                                              | `[]`                     |
+| `initContainers`                                    | Add additional init containers to the EODAG Server pod(s)                                                                 | `[]`                     |
+| `logLevel`                                          | Supported values are 0 (no log), 1 (no logging but progress bar), 2 (INFO) or 3 (DEBUG).                                  | `2`                      |
+| `productTypes`                                      | Optional overwrite of product types default configuration                                                                 | `""`                     |
+| `providers`                                         | Optional overwrite of providers default configuration                                                                     | `""`                     |
+| `config`                                            | EODAG configuration                                                                                                       | `{}`                     |
+| `configExistingSecret.name`                         | Existing secret name for EODAG config. If this is set, value config will be ignored                                       | `nil`                    |
+| `configExistingSecret.key`                          | Existing secret key for EODAG config. If this is set, value config will be ignored                                        | `nil`                    |
+| `service.type`                                      | Kubernetes service type                                                                                                   | `ClusterIP`              |
+| `service.http.enabled`                              | Enable http port on service                                                                                               | `true`                   |
+| `service.ports.http`                                | EODAG Server service HTTP port                                                                                            | `8080`                   |
+| `service.nodePorts`                                 | Specify the nodePort values for the LoadBalancer and NodePort service types.                                              | `{}`                     |
+| `service.sessionAffinity`                           | Control where client requests go, to the same pod or round-robin                                                          | `None`                   |
+| `service.sessionAffinityConfig`                     | Additional settings for the sessionAffinity                                                                               | `{}`                     |
+| `service.clusterIP`                                 | EODAG Server service clusterIP IP                                                                                         | `""`                     |
+| `service.loadBalancerIP`                            | loadBalancerIP for the SuiteCRM Service (optional, cloud specific)                                                        | `""`                     |
+| `service.loadBalancerSourceRanges`                  | Address that are allowed when service is LoadBalancer                                                                     | `[]`                     |
+| `service.externalTrafficPolicy`                     | Enable client source IP preservation                                                                                      | `Cluster`                |
+| `service.annotations`                               | Additional custom annotations for EODAG Server service                                                                    | `{}`                     |
+| `service.extraPorts`                                | Extra port to expose on EODAG Server service                                                                              | `[]`                     |
+| `ingress.enabled`                                   | Enable the creation of an ingress for the EODAG Server                                                                    | `false`                  |
+| `ingress.pathType`                                  | Path type for the EODAG Server ingress                                                                                    | `ImplementationSpecific` |
+| `ingress.apiVersion`                                | Ingress API version for the EODAG Server ingress                                                                          | `""`                     |
+| `ingress.hostname`                                  | Ingress hostname for the EODAG Server ingress                                                                             | `eodag.local`            |
+| `ingress.annotations`                               | Annotations for the EODAG Server ingress. To enable certificate autogeneration, place here your cert-manager annotations. | `{}`                     |
+| `ingress.tls`                                       | Enable TLS for the EODAG Server ingress                                                                                   | `false`                  |
+| `ingress.extraHosts`                                | Extra hosts array for the EODAG Server ingress                                                                            | `[]`                     |
+| `ingress.path`                                      | Path array for the EODAG Server ingress                                                                                   | `/`                      |
+| `ingress.extraPaths`                                | Extra paths for the EODAG Server ingress                                                                                  | `[]`                     |
+| `ingress.extraTls`                                  | Extra TLS configuration for the EODAG Server ingress                                                                      | `[]`                     |
+| `ingress.secrets`                                   | Secrets array to mount into the Ingress                                                                                   | `[]`                     |
+| `ingress.ingressClassName`                          | IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+)                                             | `""`                     |
+| `ingress.selfSigned`                                | Create a TLS secret for this ingress record using self-signed certificates generated by Helm                              | `false`                  |
+| `ingress.servicePort`                               | Backend service port to use                                                                                               | `http`                   |
+| `ingress.extraRules`                                | Additional rules to be covered with this ingress record                                                                   | `[]`                     |
+| `serviceAccount.create`                             | Specifies whether a ServiceAccount should be created                                                                      | `true`                   |
+| `serviceAccount.name`                               | The name of the ServiceAccount to use.                                                                                    | `""`                     |
+| `serviceAccount.annotations`                        | Additional custom annotations for the ServiceAccount                                                                      | `{}`                     |
+| `serviceAccount.automountServiceAccountToken`       | Automount service account token for the server service account                                                            | `true`                   |
+
+
+```console
+$ helm install my-release \
+  --set image.pullPolicy=Always \
+    my-repo/eodag-server
+```
+
+The above command sets the `image.pullPolicy` to `Always`.
+
+Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example,
+
+```console
+helm install my-release -f values.yaml my-repo/eodag-server
+```
+
+> **Tip**: You can use the default [values.yaml](values.yaml)
+
+## Configuration and installation details
+
+### [Rolling VS Immutable tags](https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/)
+
+It is strongly recommended to use immutable tags in a production environment. This ensures your deployment does not change automatically if the same tag is updated with a different image.
+
+CS Group will release a new chart updating its containers if a new version of the main container, significant changes, or critical vulnerabilities exist.
diff --git a/charts/eodag-server/templates/NOTES.txt b/charts/eodag-server/templates/NOTES.txt
new file mode 100644
index 00000000..ea633f7d
--- /dev/null
+++ b/charts/eodag-server/templates/NOTES.txt
@@ -0,0 +1,41 @@
+CHART NAME: {{ .Chart.Name  }}
+CHART VERSION: {{ .Chart.Version  }}
+APP VERSION: {{ .Chart.AppVersion  }}
+
+** Please be patient while the chart is being deployed **
+
+1. Access your EODAG Server installation:
+
+{{- if .Values.ingress.enabled }}
+  Connect to one of the following hosts:
+  {{ if .Values.ingress.tls }}
+    https://{{ .Values.ingress.hostname }}
+  {{- else }}
+    http://{{ .Values.ingress.hostname }}
+  {{- end }}
+{{- else }}
+  Execute the following commands:
+{{- if contains "NodePort" .Values.service.type }}
+  export NODE_PORT=$(kubectl get --namespace {{ include "common.names.namespace" . }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "common.names.fullname" . }})
+  export NODE_IP=$(kubectl get nodes --namespace {{ include "common.names.namespace" . }} -o jsonpath="{.items[0].status.addresses[0].address}")
+  export URL="http://${NODE_IP}:${NODE_PORT}/"
+  echo "EODAG Server URL: http://$NODE_IP:$NODE_PORT/"
+
+{{- else if contains "LoadBalancer" .Values.service.type }}
+
+** Please ensure an external IP is associated to the {{ include "common.names.fullname" . }} service before proceeding **
+** Watch the status using: kubectl get svc --namespace {{ include "common.names.namespace" . }} -w {{ include "common.names.fullname" . }} **
+
+  export SERVICE_IP=$(kubectl get svc --namespace {{ include "common.names.namespace" . }} {{ include "common.names.fullname" . }} --template "{{ "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}" }}")
+
+{{- $port:=.Values.service.ports.http | toString }}
+  export URL="http://${SERVICE_IP}{{- if ne $port "80" }}:{{ .Values.service.ports.http }}{{ end }}"
+  echo "EODAG Server URL: http://$SERVICE_IP{{- if ne $port "80" }}:{{ .Values.service.ports.http }}{{ end }}/"
+
+{{- else if contains "ClusterIP" .Values.service.type }}
+
+  kubectl port-forward --namespace {{ include "common.names.namespace" . }} svc/{{ include "common.names.fullname" . }} 8080:{{ .Values.service.ports.http }} &
+  export URL=http://127.0.0.1:8080/
+  echo "EODAG Server URL: http://127.0.0.1:8080/"
+{{- end }}
+{{- end }}
diff --git a/charts/eodag-server/templates/_helpers.tpl b/charts/eodag-server/templates/_helpers.tpl
new file mode 100644
index 00000000..06772571
--- /dev/null
+++ b/charts/eodag-server/templates/_helpers.tpl
@@ -0,0 +1,77 @@
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "eodag-server.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create -}}
+    {{ default (include "common.names.fullname" .) .Values.serviceAccount.name | trunc 63 | trimSuffix "-" }}
+{{- else -}}
+    {{ default "default" .Values.serviceAccount.name }}
+{{- end -}}
+{{- end -}}
+
+{{/*
+Return the proper Docker Image Registry Secret Names
+*/}}
+{{- define "eodag-server.imagePullSecrets" -}}
+{{- include "common.images.pullSecrets" (dict "images" (list .Values.image) "global" .Values.global) -}}
+{{- end -}}
+
+{{/*
+Get the config secret.
+*/}}
+{{- define "eodag-server.configSecretName" -}}
+{{- if .Values.configExistingSecret.name }}
+    {{- printf "%s" (tpl .Values.configExistingSecret.name $) -}}
+{{- else -}}
+    {{- printf "%s-config" (include "common.names.fullname" .) -}}
+{{- end -}}
+{{- end -}}
+
+{{/*
+Get the client secret key.
+*/}}
+{{- define "eodag-server.configSecretKey" -}}
+{{- if .Values.configExistingSecret.key }}
+    {{- printf "%s" (tpl .Values.configExistingSecret.key $) -}}
+{{- else -}}
+    {{- "eodag.yml" -}}
+{{- end -}}
+{{- end -}}
+
+{{/*
+Return  the proper Storage Class
+*/}}
+{{- define "eodag-server.storageClass" -}}
+{{- include "common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) -}}
+{{- end -}}
+
+{{/*
+Create EODAG Server app version
+*/}}
+{{- define "eodag-server.defaultTag" -}}
+{{- default .Chart.AppVersion .Values.image.tag }}
+{{- end -}}
+
+{{/*
+Return the proper image name
+*/}}
+{{- define "eodag-server.image" -}}
+{{- $registryName := .Values.image.registry -}}
+{{- $repositoryName := .Values.image.repository -}}
+{{- $separator := ":" -}}
+{{- $termination := .Values.image.tag | default .Chart.AppVersion | toString -}}
+{{- if .Values.global }}
+    {{- if .Values.global.imageRegistry }}
+     {{- $registryName = .Values.global.imageRegistry -}}
+    {{- end -}}
+{{- end -}}
+{{- if .Values.image.digest }}
+    {{- $separator = "@" -}}
+    {{- $termination = .Values.image.digest | toString -}}
+{{- end -}}
+{{- if $registryName }}
+    {{- printf "%s/%s%s%s" $registryName $repositoryName $separator $termination -}}
+{{- else -}}
+    {{- printf "%s%s%s"  $repositoryName $separator $termination -}}
+{{- end -}}
+{{- end -}}
diff --git a/charts/eodag-server/templates/configmap.yaml b/charts/eodag-server/templates/configmap.yaml
new file mode 100644
index 00000000..bd76e7c5
--- /dev/null
+++ b/charts/eodag-server/templates/configmap.yaml
@@ -0,0 +1,23 @@
+{{- if or .Values.productTypes .Values.providers }}
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ printf "%s-config" (include "common.names.fullname" .) }}
+  namespace: {{ include "common.names.namespace" . | quote }}
+  labels: {{- include "common.labels.standard" . | nindent 4 }}
+    {{- if .Values.commonLabels }}
+    {{- include "common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }}
+    {{- end }}
+  {{- if .Values.commonAnnotations }}
+  annotations: {{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }}
+  {{- end }}
+data:
+  {{- if .Values.productTypes }}
+  product_types.yml: |-
+    {{- .Values.productTypes | nindent 4 }}
+  {{- end }}
+  {{- if .Values.providers }}
+  providers.yml: |-
+    {{- .Values.providers | nindent 4 }}
+  {{- end }}
+{{- end }}
diff --git a/charts/eodag-server/templates/deployment.yaml b/charts/eodag-server/templates/deployment.yaml
new file mode 100644
index 00000000..33efeeab
--- /dev/null
+++ b/charts/eodag-server/templates/deployment.yaml
@@ -0,0 +1,219 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "common.names.fullname" . }}
+  namespace: {{ include "common.names.namespace" . | quote }}
+  labels: {{- include "common.labels.standard" . | nindent 4 }}
+    {{- if .Values.commonLabels }}
+    {{- include "common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }}
+    {{- end }}
+  {{- if .Values.commonAnnotations }}
+  annotations: {{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }}
+  {{- end }}
+spec:
+  replicas: {{ .Values.replicaCount }}
+  {{- if .Values.updateStrategy }}
+  strategy: {{- toYaml .Values.updateStrategy | nindent 4 }}
+  {{- end }}
+  selector:
+    matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }}
+  template:
+    metadata:
+      labels: {{- include "common.labels.standard" . | nindent 8 }}
+        {{- if .Values.podLabels }}
+        {{- include "common.tplvalues.render" (dict "value" .Values.podLabels "context" $) | nindent 8 }}
+        {{- end }}
+      annotations:
+        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
+        {{- if .Values.podAnnotations }}
+        {{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }}
+        {{- end }}
+    spec:
+      serviceAccountName: {{ include "eodag-server.serviceAccountName" . }}
+      {{- include "eodag-server.imagePullSecrets" . | nindent 6 }}
+      {{- if .Values.hostAliases }}
+      hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.hostAliases "context" $) | nindent 8 }}
+      {{- end }}
+      {{- if .Values.affinity }}
+      affinity: {{- include "common.tplvalues.render" (dict "value" .Values.affinity "context" $) | nindent 8 }}
+      {{- else if or .Values.podAffinityPreset .Values.podAntiAffinityPreset .Values.nodeAffinityPreset }}
+      affinity:
+        podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAffinityPreset "instance" .Chart.Name "context" $) | nindent 10 }}
+        podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset "instance" .Chart.Name "context" $) | nindent 10 }}
+        nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values) | nindent 10 }}
+      {{- end }}
+      {{- if .Values.nodeSelector }}
+      nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.nodeSelector "context" $) | nindent 8 }}
+      {{- end }}
+      {{- if .Values.tolerations }}
+      tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.tolerations "context" .) | nindent 8 }}
+      {{- end }}
+      {{- if .Values.schedulerName }}
+      schedulerName: {{ .Values.schedulerName }}
+      {{- end }}
+      {{- if .Values.shareProcessNamespace }}
+      shareProcessNamespace: {{ .Values.shareProcessNamespace }}
+      {{- end }}
+      {{- if .Values.topologySpreadConstraints }}
+      topologySpreadConstraints: {{- include "common.tplvalues.render" (dict "value" .Values.topologySpreadConstraints "context" .) | nindent 8 }}
+      {{- end }}
+      {{- if .Values.priorityClassName }}
+      priorityClassName: {{ .Values.priorityClassName | quote }}
+      {{- end }}
+      {{- if .Values.runtimeClassName }}
+      runtimeClassName: {{ .Values.runtimeClassName }}
+      {{- end }}
+      {{- if .Values.podSecurityContext.enabled }}
+      securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }}
+      {{- end }}
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- if .Values.initContainers }}
+      initContainers: {{- include "common.tplvalues.render" (dict "value" .Values.initContainers "context" $) | nindent 8 }}
+      {{- end }}
+      containers:
+        - name: eodag-server
+          image: {{ include "eodag-server.image" . }}
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          {{- if .Values.lifecycleHooks }}
+          lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.lifecycleHooks "context" $) | nindent 12 }}
+          {{- end }}
+          {{- if .Values.containerSecurityContext.enabled }}
+          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
+          {{- end }}
+          {{- if .Values.command }}
+          command: {{- include "common.tplvalues.render" (dict "value" .Values.command "context" $) | nindent 12 }}
+          {{- end }}
+          {{- if .Values.args }}
+          args: {{- include "common.tplvalues.render" (dict "value" .Values.args "context" $) | nindent 12 }}
+          {{- end }}
+          ports:
+            - name: http
+              containerPort: {{ .Values.containerPorts.http }}
+              protocol: TCP
+          {{- if .Values.customStartupProbe }}
+          startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }}
+          {{- else if .Values.startupProbe.enabled }}
+          startupProbe:
+            httpGet:
+              path: /
+              port: {{ .Values.containerPorts.http }}
+            initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }}
+            periodSeconds: {{ .Values.startupProbe.periodSeconds }}
+            timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }}
+            successThreshold: {{ .Values.startupProbe.successThreshold }}
+            failureThreshold: {{ .Values.startupProbe.failureThreshold }}
+          {{- end }}
+          {{- if .Values.customLivenessProbe }}
+          livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }}
+          {{- else if .Values.livenessProbe.enabled }}
+          livenessProbe:
+            httpGet:
+              path: /
+              port: {{ .Values.containerPorts.http }}
+            initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
+            periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
+            timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
+            successThreshold: {{ .Values.livenessProbe.successThreshold }}
+            failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
+          {{- end }}
+          {{- if .Values.customReadinessProbe }}
+          readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }}
+          {{- else if .Values.readinessProbe.enabled }}
+          readinessProbe:
+            httpGet:
+              path: /
+              port: {{ .Values.containerPorts.http }}
+            initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
+            periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
+            timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
+            successThreshold: {{ .Values.readinessProbe.successThreshold }}
+            failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
+          {{- end }}
+          env:
+            {{- if or .Values.extraEnvVars }}
+            {{- include "common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }}
+            {{- end }}
+            {{- if or .Values.config .Values.configExistingSecret.name }}
+            - name: EODAG_CFG_FILE
+              value: /eodag/eodag.yml
+            {{- end }}
+            {{- if gt (.Values.logLevel | int) 0 }}
+            - name: EODAG_LOGGING
+              value: {{ .Values.logLevel | quote }}
+            {{- end }}
+          {{- if or .Values.extraEnvVarsCM .Values.extraEnvVarsSecret }}
+          envFrom:
+            {{- if .Values.extraEnvVarsCM }}
+            - configMapRef:
+                name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsCM "context" $) }}
+            {{- end }}
+            {{- if .Values.extraEnvVarsSecret }}
+            - secretRef:
+                name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsSecret "context" $) }}
+            {{- end }}
+          {{- end }}
+          {{- if .Values.resources }}
+          resources: {{- toYaml .Values.resources | nindent 12 }}
+          {{- end }}
+          volumeMounts:
+            {{- if .Values.productTypes }}
+            - name: config
+              mountPath: /eodag/eodag/resources/product_types.yml
+              subPath: product_types.yml
+            {{- end }}
+            {{- if .Values.providers }}
+            - name: config
+              mountPath: /eodag/eodag/resources/providers.yml
+              subPath: providers.yml
+            {{- end }}
+            {{- if or .Values.config .Values.configExistingSecret.name }}
+            - name: eodag-config
+              mountPath: /eodag/eodag.yml
+              subPath: eodag.yml
+            {{- end }}
+            - name: download-data
+              mountPath: /tmp
+            {{- if .Values.extraVolumeMounts }}
+            {{- toYaml .Values.extraVolumeMounts | nindent 12 }}
+            {{- end }}
+        {{- if .Values.sidecars }}
+        {{- include "common.tplvalues.render" (dict "value" .Values.sidecars "context" $) | nindent 8 }}
+        {{- end }}
+      volumes:
+        {{- if or .Values.productTypes .Values.providers }}
+        - name: config
+          configMap:
+            name: {{ printf "%s-config" (include "common.names.fullname" .) }}
+        {{- end }}
+        {{- if or .Values.config .Values.configExistingSecret.name }}
+        - name: eodag-config
+          secret:
+            secretName: {{ include "eodag-server.configSecretName" . }}
+            items:
+              - key: {{ include "eodag-server.configSecretKey" . }}
+                path: {{ include "eodag-server.configSecretKey" . }}
+        {{- end }}
+        {{- if .Values.persistence.enabled }}
+        - name: download-data
+          persistentVolumeClaim:
+            claimName: {{ .Values.persistence.existingClaim | default (printf "%s-download-data" (include "common.names.fullname" .)) }}
+        {{- else }}
+        - name: download-data
+          {{- if or .Values.persistence.medium .Values.persistence.sizeLimit }}
+          emptyDir:
+            {{- if .Values.persistence.medium }}
+            medium: {{ .Values.persistence.medium | quote }}
+            {{- end }}
+            {{- if .Values.persistence.sizeLimit }}
+            sizeLimit: {{ .Values.persistence.sizeLimit | quote }}
+            {{- end }}
+          {{- else }}
+          emptyDir: {}
+          {{- end }}
+        {{- end }}
+        {{- if .Values.extraVolumes }}
+        {{- toYaml .Values.extraVolumes | nindent 8 }}
+        {{- end }}
diff --git a/charts/eodag-server/templates/extra-list.yaml b/charts/eodag-server/templates/extra-list.yaml
new file mode 100644
index 00000000..9ac65f9e
--- /dev/null
+++ b/charts/eodag-server/templates/extra-list.yaml
@@ -0,0 +1,4 @@
+{{- range .Values.extraDeploy }}
+---
+{{ include "common.tplvalues.render" (dict "value" . "context" $) }}
+{{- end }}
diff --git a/charts/eodag-server/templates/ingress.yaml b/charts/eodag-server/templates/ingress.yaml
new file mode 100644
index 00000000..9e070a11
--- /dev/null
+++ b/charts/eodag-server/templates/ingress.yaml
@@ -0,0 +1,62 @@
+{{- if .Values.ingress.enabled -}}
+apiVersion: {{ include "common.capabilities.ingress.apiVersion" . }}
+kind: Ingress
+metadata:
+  name: {{ include "common.names.fullname" . }}
+  namespace: {{ include "common.names.namespace" . | quote }}
+  labels: {{- include "common.labels.standard" . | nindent 4 }}
+    {{- if .Values.commonLabels }}
+    {{- include "common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }}
+    {{- end }}
+  {{- if or .Values.ingress.annotations .Values.commonAnnotations }}
+  annotations:
+    {{- if .Values.ingress.annotations }}
+    {{- include "common.tplvalues.render" (dict "value" .Values.ingress.annotations "context" $) | nindent 4 }}
+    {{- end }}
+    {{- if .Values.commonAnnotations }}
+    {{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }}
+    {{- end }}
+  {{- end }}
+spec:
+  {{- if and .Values.ingress.ingressClassName (eq "true" (include "common.ingress.supportsIngressClassname" .)) }}
+  ingressClassName: {{ .Values.ingress.ingressClassName | quote }}
+  {{- end }}
+  rules:
+    {{- if .Values.ingress.hostname }}
+    - host: {{ .Values.ingress.hostname }}
+      http:
+        paths:
+          {{- if .Values.ingress.extraPaths }}
+          {{- toYaml .Values.ingress.extraPaths | nindent 10 }}
+          {{- end }}
+          - path: {{ .Values.ingress.path }}
+            {{- if eq "true" (include "common.ingress.supportsPathType" .) }}
+            pathType: {{ .Values.ingress.pathType }}
+            {{- end }}
+            backend: {{- include "common.ingress.backend" (dict "serviceName" (include "common.names.fullname" .) "servicePort" .Values.ingress.servicePort "context" $)  | nindent 14 }}
+    {{- end }}
+    {{- range .Values.ingress.extraHosts }}
+    - host: {{ .name | quote }}
+      http:
+        paths:
+          - path: {{ default "/" .path }}
+            {{- if eq "true" (include "common.ingress.supportsPathType" $) }}
+            pathType: {{ default "ImplementationSpecific" .pathType }}
+            {{- end }}
+            backend: {{- include "common.ingress.backend" (dict "serviceName" (include "common.names.fullname" $) "servicePort" .Values.ingress.servicePort "context" $) | nindent 14 }}
+    {{- end }}
+    {{- if .Values.ingress.extraRules }}
+    {{- include "common.tplvalues.render" (dict "value" .Values.ingress.extraRules "context" $) | nindent 4 }}
+    {{- end }}
+  {{- if or .Values.ingress.tls .Values.ingress.extraTls }}
+  tls:
+    {{- if .Values.ingress.tls }}
+    - hosts:
+        - {{ .Values.ingress.hostname }}
+      secretName: {{ printf "%s-tls" .Values.ingress.hostname }}
+    {{- end }}
+    {{- if .Values.ingress.extraTls }}
+    {{- include "common.tplvalues.render" (dict "value" .Values.ingress.extraTls "context" $) | nindent 4 }}
+    {{- end }}
+  {{- end }}
+{{- end -}}
diff --git a/charts/eodag-server/templates/pv.yaml b/charts/eodag-server/templates/pv.yaml
new file mode 100644
index 00000000..d51081cc
--- /dev/null
+++ b/charts/eodag-server/templates/pv.yaml
@@ -0,0 +1,25 @@
+{{- if and .Values.persistence.enabled .Values.persistence.hostPath (not .Values.persistence.existingClaim) -}}
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+  name: {{ include "common.names.fullname" . }}-matomo
+  namespace: {{ include "common.names.namespace" . | quote }}
+  labels: {{- include "common.labels.standard" . | nindent 4 }}
+    {{- if .Values.commonLabels }}
+    {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
+    {{- end }}
+  {{- if .Values.commonAnnotations }}
+  annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
+  {{- end }}
+spec:
+  {{- if not (empty .Values.persistence.accessModes) }}
+  accessModes:
+  {{- range .Values.persistence.accessModes }}
+    - {{ . | quote }}
+  {{- end }}
+  {{- end }}
+  capacity:
+    storage: {{ .Values.persistence.size | quote }}
+  hostPath:
+    path: {{ .Values.persistence.hostPath | quote }}
+{{- end -}}
diff --git a/charts/eodag-server/templates/pvc.yaml b/charts/eodag-server/templates/pvc.yaml
new file mode 100644
index 00000000..6c145bdc
--- /dev/null
+++ b/charts/eodag-server/templates/pvc.yaml
@@ -0,0 +1,44 @@
+{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}}
+kind: PersistentVolumeClaim
+apiVersion: v1
+metadata:
+  name: {{ include "common.names.fullname" . }}-download-data
+  namespace: {{ include "common.names.namespace" . | quote }}
+  labels: {{- include "common.labels.standard" . | nindent 4 }}
+    {{- if .Values.commonLabels }}
+    {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
+    {{- end }}
+    {{- if .Values.persistence.labels }}
+    {{- toYaml .Values.persistence.labels | nindent 4 }}
+    {{- end }}
+  {{- if or .Values.persistence.annotations .Values.commonAnnotations }}
+  annotations:
+    {{- if .Values.commonAnnotations }}
+    {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
+    {{- end }}
+    {{- if .Values.persistence.annotations }}
+    {{- include "common.tplvalues.render" ( dict "value" .Values.persistence.annotations "context" $ ) | nindent 4 }}
+    {{- end }}
+  {{- end }}
+spec:
+  {{- if .Values.persistence.hostPath }}
+  storageClassName: ""
+  {{- else }}
+  {{- include "eodag-server.storageClass" . | nindent 2 }}
+  {{- end }}
+  {{- if not (empty .Values.persistence.accessModes) }}
+  accessModes:
+  {{- range .Values.persistence.accessModes }}
+    - {{ . | quote }}
+  {{- end }}
+  {{- end }}
+  resources:
+    requests:
+      storage: {{ .Values.persistence.size | quote }}
+  {{- if .Values.persistence.selector }}
+  selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.selector "context" $) | nindent 4 }}
+  {{- end -}}
+  {{- if .Values.persistence.dataSource }}
+  dataSource: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.dataSource "context" $) | nindent 4 }}
+  {{- end }}
+{{- end -}}
diff --git a/charts/eodag-server/templates/secret.yaml b/charts/eodag-server/templates/secret.yaml
new file mode 100644
index 00000000..4c11aaf9
--- /dev/null
+++ b/charts/eodag-server/templates/secret.yaml
@@ -0,0 +1,17 @@
+{{- if and .Values.config (not .Values.configExistingSecret.name) }}
+apiVersion: v1
+kind: Secret
+metadata:
+  name: {{ printf "%s-config" (include "common.names.fullname" .) }}
+  namespace: {{ include "common.names.namespace" . | quote }}
+  labels: {{- include "common.labels.standard" . | nindent 4 }}
+    {{- if .Values.commonLabels }}
+    {{- include "common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }}
+    {{- end }}
+  {{- if .Values.commonAnnotations }}
+  annotations: {{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }}
+  {{- end }}
+type: Opaque
+data:
+  eodag.yml: {{ include "common.tplvalues.render" (dict "value" .Values.config "context" $) | b64enc }}
+{{- end }}
diff --git a/charts/eodag-server/templates/service.yaml b/charts/eodag-server/templates/service.yaml
new file mode 100644
index 00000000..4cbcbd0a
--- /dev/null
+++ b/charts/eodag-server/templates/service.yaml
@@ -0,0 +1,52 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "common.names.fullname" . }}
+  namespace: {{ include "common.names.namespace" . | quote }}
+  labels: {{- include "common.labels.standard" . | nindent 4 }}
+    {{- if .Values.commonLabels }}
+    {{- include "common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }}
+    {{- end }}
+  {{- if or .Values.commonAnnotations .Values.service.annotations }}
+  annotations:
+  {{- if .Values.commonAnnotations }}
+  {{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }}
+  {{- end }}
+  {{- if .Values.service.annotations }}
+  {{- include "common.tplvalues.render" (dict "value" .Values.service.annotations "context" $) | nindent 4 }}
+  {{- end }}
+  {{- end }}
+spec:
+  type: {{ .Values.service.type }}
+  {{- if and .Values.service.clusterIP (eq .Values.service.type "ClusterIP") }}
+  clusterIP: {{ .Values.service.clusterIP }}
+  {{- end }}
+  {{- if (or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort")) }}
+  externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy | quote }}
+  {{- end }}
+  {{- if eq .Values.service.type "LoadBalancer" }}
+  loadBalancerSourceRanges: {{ .Values.service.loadBalancerSourceRanges }}
+  {{- end }}
+  {{- if (and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerIP))) }}
+  loadBalancerIP: {{ .Values.service.loadBalancerIP }}
+  {{- end }}
+  {{- if .Values.service.sessionAffinity }}
+  sessionAffinity: {{ .Values.service.sessionAffinity }}
+  {{- end }}
+  {{- if .Values.service.sessionAffinityConfig }}
+  sessionAffinityConfig: {{- include "common.tplvalues.render" (dict "value" .Values.service.sessionAffinityConfig "context" $) | nindent 4 }}
+  {{- end }}
+  ports:
+    - port: {{ .Values.service.ports.http }}
+      targetPort: http
+      protocol: TCP
+      name: http
+      {{- if (and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePort))) }}
+      nodePort: {{ .Values.service.nodePorts.http }}
+      {{- else if eq .Values.service.type "ClusterIP" }}
+      nodePort: null
+      {{- end }}
+    {{- if .Values.service.extraPorts }}
+    {{- include "common.tplvalues.render" (dict "value" .Values.service.extraPorts "context" $) | nindent 4 }}
+    {{- end }}
+  selector: {{- include "common.labels.matchLabels" . | nindent 4 }}
diff --git a/charts/eodag-server/templates/serviceaccount.yaml b/charts/eodag-server/templates/serviceaccount.yaml
new file mode 100644
index 00000000..f4bde06d
--- /dev/null
+++ b/charts/eodag-server/templates/serviceaccount.yaml
@@ -0,0 +1,21 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "eodag-server.serviceAccountName" . }}
+  namespace: {{ include "common.names.namespace" . | quote }}
+  labels: {{- include "common.labels.standard" . | nindent 4 }}
+    {{- if .Values.commonLabels }}
+    {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
+    {{- end }}
+  {{- if or .Values.serviceAccount.annotations .Values.commonAnnotations }}
+  annotations:
+    {{- if .Values.commonAnnotations }}
+      {{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }}
+    {{- end }}
+    {{- if .Values.serviceAccount.annotations }}
+      {{- include "common.tplvalues.render" (dict "value" .Values.serviceAccount.annotations "context" $) | nindent 4 }}
+    {{- end }}
+  {{- end }}
+automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
+{{- end }}
diff --git a/charts/eodag-server/values.yaml b/charts/eodag-server/values.yaml
new file mode 100644
index 00000000..c1acef6b
--- /dev/null
+++ b/charts/eodag-server/values.yaml
@@ -0,0 +1,692 @@
+## @section Global parameters
+## Global Docker image parameters
+## Please, note that this will override the image parameters, including dependencies, configured to use the global value
+## Current available global Docker image parameters: imageRegistry, imagePullSecrets and storageClass
+##
+
+## @param global.imageRegistry Global Docker image registry
+## @param global.imagePullSecrets Global Docker registry secret names as an array
+## @param global.storageClass Global StorageClass for Persistent Volume(s)
+##
+global:
+  imageRegistry: ""
+  ## E.g.
+  ## imagePullSecrets:
+  ##   - myRegistryKeySecretName
+  ##
+  imagePullSecrets: []
+  storageClass: ""
+
+## @section Common parameters
+##
+
+## @param kubeVersion Force target Kubernetes version (using Helm capabilities if not set)
+##
+kubeVersion: ""
+## @param nameOverride String to partially override common.names.fullname
+##
+nameOverride: ""
+## @param fullnameOverride String to fully override common.names.fullname
+##
+fullnameOverride: ""
+## @param namespaceOverride String to fully override common.names.namespaceapi
+##
+namespaceOverride: ""
+## @param commonLabels Labels to add to all deployed objects
+##
+commonLabels: {}
+## @param commonAnnotations Annotations to add to all deployed objects
+##
+commonAnnotations: {}
+## @param clusterDomain Kubernetes cluster domain name
+##
+clusterDomain: cluster.local
+## @param extraDeploy Array of extra objects to deploy with the release
+##
+extraDeploy: []
+
+## @section EODAG Server parameters
+
+## CS Group EODAG Server image version
+## ref: https://hub.docker.com/r/csspace/eodag-server
+## @param image.registry EODAG Server image registry
+## @param image.repository EODAG Server image repository
+## @param image.tag Overrides the EODAG Server image tag whose default is the chart appVersion (immutable tags are recommended)
+## @param image.digest EODAG Server image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag
+## @param image.pullPolicy EODAG Server image pull policy
+## @param image.pullSecrets Specify docker-registry secret names as an array
+## Number of replicas to deploy
+##
+image:
+  registry: docker.io
+  repository: csspace/eodag-server
+  tag: ""
+  digest: ""
+  ## Specify a imagePullPolicy
+  ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent'
+  ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images
+  ##
+  pullPolicy: IfNotPresent
+  ## Optionally specify an array of imagePullSecrets.
+  ## Secrets must be manually created in the namespace.
+  ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
+  ## Example:
+  ## pullSecrets:
+  ##   - myRegistryKeySecretName
+  ##
+  pullSecrets: []
+
+## @param replicaCount Number of EODAG Server replicas
+##
+replicaCount: 1
+
+## Configure extra options for EODAG Server containers' liveness and readiness probes
+## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes
+## @param startupProbe.enabled Enable startupProbe on EODAG Server containers
+## @param startupProbe.initialDelaySeconds Initial delay seconds for startupProbe
+## @param startupProbe.periodSeconds Period seconds for startupProbe
+## @param startupProbe.timeoutSeconds Timeout seconds for startupProbe
+## @param startupProbe.failureThreshold Failure threshold for startupProbe
+## @param startupProbe.successThreshold Success threshold for startupProbe
+##
+startupProbe:
+  enabled: false
+  initialDelaySeconds: 10
+  periodSeconds: 10
+  timeoutSeconds: 1
+  failureThreshold: 3
+  successThreshold: 1
+## @param livenessProbe.enabled Enable livenessProbe on EODAG Server containers
+## @param livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe
+## @param livenessProbe.periodSeconds Period seconds for livenessProbe
+## @param livenessProbe.timeoutSeconds Timeout seconds for livenessProbe
+## @param livenessProbe.failureThreshold Failure threshold for livenessProbe
+## @param livenessProbe.successThreshold Success threshold for livenessProbe
+##
+livenessProbe:
+  enabled: false
+  initialDelaySeconds: 3
+  periodSeconds: 10
+  timeoutSeconds: 1
+  failureThreshold: 3
+  successThreshold: 1
+## @param readinessProbe.enabled Enable readinessProbe on EODAG Server containers
+## @param readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe
+## @param readinessProbe.periodSeconds Period seconds for readinessProbe
+## @param readinessProbe.timeoutSeconds Timeout seconds for readinessProbe
+## @param readinessProbe.failureThreshold Failure threshold for readinessProbe
+## @param readinessProbe.successThreshold Success threshold for readinessProbe
+##
+readinessProbe:
+  enabled: false
+  initialDelaySeconds: 3
+  periodSeconds: 10
+  timeoutSeconds: 1
+  failureThreshold: 3
+  successThreshold: 1
+
+## @param customLivenessProbe Custom livenessProbe that overrides the default one
+##
+customLivenessProbe: {}
+
+## @param customReadinessProbe Custom readinessProbe that overrides the default one
+##
+customReadinessProbe: {}
+
+## EODAG Server resource requests and limits
+## ref: https://kubernetes.io/docs/user-guide/compute-resources/
+## @param resources.limits The resources limits for the EODAG Server containers
+## @param resources.requests The requested resources for the EODAG Server containers
+##
+resources:
+  limits: {}
+  requests: {}
+
+## Configure Pods Security Context
+## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod
+## @param podSecurityContext.enabled Enabled EODAG Server pods' Security Context
+## @param podSecurityContext.fsGroup Set EODAG Server pod's Security Context fsGroup
+##
+podSecurityContext:
+  enabled: false
+  fsGroup: 1001
+
+## Configure Container Security Context
+## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod
+## @param containerSecurityContext.enabled Enabled EODAG Server containers' Security Context
+## @param containerSecurityContext.runAsUser Set EODAG Server containers' Security Context runAsUser
+## @param containerSecurityContext.allowPrivilegeEscalation Set EODAG Server containers' Security Context allowPrivilegeEscalation
+## @param containerSecurityContext.capabilities.drop Set EODAG Server containers' Security Context capabilities to be dropped
+## @param containerSecurityContext.readOnlyRootFilesystem Set EODAG Server containers' Security Context readOnlyRootFilesystem
+## @param containerSecurityContext.runAsNonRoot Set EODAG Server container's Security Context runAsNonRoot
+##
+containerSecurityContext:
+  enabled: false
+  runAsUser: 1001
+  allowPrivilegeEscalation: false
+  capabilities:
+    drop:
+    - all
+  readOnlyRootFilesystem: false
+  runAsNonRoot: true
+
+## @param command Override default container command (useful when using custom images)
+##
+command: []
+
+## @param args Override default container args (useful when using custom images). Overrides the defaultArgs.
+##
+args: []
+
+## EODAG Server application ports
+## @param containerPorts.http EODAG Server application HTTP port number
+containerPorts:
+  http: 5000
+
+## Enable persistence using Persistent Volume Claims
+## ref: https://kubernetes.io/docs/user-guide/persistent-volumes/
+##
+persistence:
+  ## @param persistence.enabled Enable persistence using PVC
+  ##
+  enabled: false
+  ## @param persistence.medium Provide a medium for `emptyDir` volumes.
+  ##
+  medium: ""
+  ## @param persistence.sizeLimit Set this to enable a size limit for `emptyDir` volumes.
+  ##
+  sizeLimit: 8Gi
+  ## @param persistence.storageClass PVC Storage Class for Matomo volume
+  ## If defined, storageClassName: <storageClass>
+  ## If set to "-", storageClassName: "", which disables dynamic provisioning
+  ## If undefined (the default) or set to null, no storageClassName spec is
+  ##   set, choosing the default provisioner.  (gp2 on AWS, standard on
+  ##   GKE, AWS & OpenStack)
+  ##
+  storageClass: ""
+  ## @param persistence.accessModes PVC Access Mode for Matomo volume
+  ## Requires persistence.enabled: true
+  ## If defined, PVC must be created manually before volume will be bound
+  ##
+  accessModes:
+    - ReadWriteOnce
+  ## @param persistence.size PVC Storage Request for Matomo volume
+  ##
+  size: 8Gi
+  ## @param persistence.dataSource Custom PVC data source
+  ##
+  dataSource: {}
+  ## @param persistence.existingClaim A manually managed Persistent Volume Claim
+  ## Requires persistence.enabled: true
+  ## If defined, PVC must be created manually before volume will be bound
+  ##
+  existingClaim: ""
+  ## @param persistence.hostPath If defined, the matomo-data volume will mount to the specified hostPath.
+  ## Requires persistence.enabled: true
+  ## Requires persistence.existingClaim: nil|false
+  ## Default: nil.
+  ##
+  hostPath: ""
+  ## @param persistence.annotations Persistent Volume Claim annotations
+  ##
+  annotations: {}
+  ## @param persistence.labels Additional custom labels for the PVC
+  ##
+  labels: {}
+  ## @param persistence.selector Selector to match an existing Persistent Volume for Matomo data PVC
+  ## If set, the PVC can't have a PV dynamically provisioned for it
+  ## E.g.
+  ## selector:
+  ##   matchLabels:
+  ##     app: my-app
+  ##
+  selector: {}
+
+## @param hostAliases EODAG Server pods host aliases
+## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/
+##
+hostAliases: []
+
+## @param podLabels Extra labels for EODAG Server pods
+## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
+##
+podLabels: {}
+
+## @param podAnnotations Annotations for EODAG Server pods
+## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
+##
+podAnnotations: {}
+
+## @param podAffinityPreset Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard`
+## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity
+##
+podAffinityPreset: ""
+
+## @param podAntiAffinityPreset Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard`
+## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity
+##
+podAntiAffinityPreset: soft
+
+## Node affinity preset
+## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity
+##
+nodeAffinityPreset:
+  ## @param nodeAffinityPreset.type Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard`
+  ##
+  type: ""
+  ## @param nodeAffinityPreset.key Node label key to match. Ignored if `affinity` is set
+  ##
+  key: ""
+  ## @param nodeAffinityPreset.values Node label values to match. Ignored if `affinity` is set
+  ## E.g.
+  ## values:
+  ##   - e2e-az1
+  ##   - e2e-az2
+  ##
+  values: []
+
+## @param affinity Affinity for EODAG Server pods assignment
+## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
+## NOTE: `podAffinityPreset`, `podAntiAffinityPreset`, and `nodeAffinityPreset` will be ignored when it's set
+##
+affinity: {}
+
+## @param nodeSelector Node labels for EODAG Server pods assignment
+## ref: https://kubernetes.io/docs/user-guide/node-selection/
+##
+nodeSelector: {}
+
+## @param tolerations Tolerations for EODAG Server pods assignment
+## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
+##
+tolerations: []
+
+## @param schedulerName Name of the k8s scheduler (other than default)
+## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/
+##
+schedulerName: ""
+
+## @param shareProcessNamespace Enable shared process namespace in a pod.
+## If set to false (default), each container will run in separate namespace,  will have PID=1.
+## If set to true, the /pause will run as init process and will reap any zombie PIDs,
+## for example, generated by a custom exec probe running longer than a probe timeoutSeconds.
+## Enable this only if customLivenessProbe or customReadinessProbe is used and zombie PIDs are accumulating.
+## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/
+##
+shareProcessNamespace: false
+
+## @param topologySpreadConstraints Topology Spread Constraints for pod assignment
+## https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
+## The value is evaluated as a template
+##
+topologySpreadConstraints: []
+
+## @param updateStrategy.type EODAG Server statefulset strategy type
+## ref: https://kubernetes.io/docs/concepts/workloads/s/statefulset/#update-strategies
+##
+updateStrategy:
+  ## StrategyType
+  ## Can be set to RollingUpdate or OnDelete
+  ##
+  type: RollingUpdate
+
+## @param priorityClassName EODAG Server pods' priorityClassName
+##
+priorityClassName: ""
+
+## @param runtimeClassName Name of the runtime class to be used by pod(s)
+## ref: https://kubernetes.io/docs/concepts/containers/runtime-class/
+##
+runtimeClassName: ""
+
+## @param lifecycleHooks for the EODAG Server container(s) to automate configuration before or after startup
+##
+lifecycleHooks: {}
+
+## @param extraEnvVars Array with extra environment variables to add to EODAG Server nodes
+## e.g:
+## extraEnvVars:
+##   - name: FOO
+##     value: "bar"
+##
+extraEnvVars: []
+
+## @param extraEnvVarsCM Name of existing ConfigMap containing extra env vars for EODAG Server nodes
+##
+extraEnvVarsCM: ""
+
+## @param extraEnvVarsSecret Name of existing Secret containing extra env vars for EODAG Server nodes
+##
+extraEnvVarsSecret: ""
+
+## @param extraVolumes Optionally specify extra list of additional volumes for the EODAG Server pod(s)
+##
+extraVolumes: []
+
+## @param extraVolumeMounts Optionally specify extra list of additional volumeMounts for the EODAG Server container(s)
+##
+extraVolumeMounts: []
+
+## @param sidecars Add additional sidecar containers to the EODAG Server pod(s)
+## e.g:
+## sidecars:
+##   - name: your-image-name
+##     image: your-image
+##     imagePullPolicy: Always
+##     ports:
+##       - name: portname
+##         containerPort: 1234
+##
+sidecars: []
+
+## @param initContainers Add additional init containers to the EODAG Server pod(s)
+## ref: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
+## e.g:
+## initContainers:
+##  - name: your-image-name
+##    image: your-image
+##    imagePullPolicy: Always
+##    command: ['sh', '-c', 'echo "hello world"']
+##
+initContainers: []
+
+## @param logLevel Supported values are 0 (no log), 1 (no logging but progress bar), 2 (INFO) or 3 (DEBUG).
+logLevel: 2
+
+## @param productTypes Optional overwrite of product types default configuration
+## ref: https://github.com/CS-SI/eodag/blob/masster/eodag/resources/product_types.yml
+productTypes: ""
+# productTypes: |-
+#   # CBERS 4 ---------------------------------------------------------------------
+#   CBERS4_MUX_L2:
+#     abstract: |
+#       China-Brazil Earth Resources Satellite, CBERS-4 MUX camera Level-2 product. System corrected images, expect some
+#       translation error.
+#     instrument: MUX
+#     platform: CBERS
+#     platformSerialIdentifier: CBERS-4
+#     processingLevel: L2
+#     keywords: MUX,CBERS,CBERS-4,L2
+#     sensorType: OPTICAL
+#     license: proprietary
+#     missionStartDate: "2014-12-07T00:00:00Z"
+#     title: CBERS-4 MUX Level-2
+
+## @param providers Optional overwrite of providers default configuration
+## ref: https://github.com/CS-SI/eodag/blob/master/eodag/resources/providers.yml
+providers: ""
+# providers: |-
+#   !provider
+#     name: usgs
+#     priority: 0
+#     description: U.S geological survey catalog for Landsat products
+#     roles:
+#       - host
+#     url: https://earthexplorer.usgs.gov/
+#     api: !plugin
+#       type: UsgsApi
+#       need_auth: true
+#       google_base_url: 'http://storage.googleapis.com/earthengine-public/landsat/'
+#       pagination:
+#         max_items_per_page: 5000
+#         total_items_nb_key_path: '$.totalHits'
+#       common_metadata_mapping_path: '$'
+#       metadata_mapping:
+#         id: '$.displayId'
+#         geometry: '$.spatialBounds'
+#         productType: '$.productType'
+#         title: '$.displayId'
+#         abstract: '$.summary'
+#         cloudCover: '$.cloudCover'
+#         startTimeFromAscendingNode: '$.temporalCoverage.startDate'
+#         completionTimeFromAscendingNode: '$.temporalCoverage.endDate'
+#         publicationDate: '$.publishDate'
+#         thumbnail: '$.browse[0].thumbnailPath'
+#         quicklook: '$.browse[0].browsePath'
+#         storageStatus: '{$.available#get_group_name((?P<ONLINE>True)|(?P<OFFLINE>False))}'
+#         downloadLink: 'https://earthexplorer.usgs.gov/download/external/options/{productType}/{entityId}/M2M/'
+#         # metadata needed for download
+#         entityId: '$.entityId'
+#         productId: '$.id'
+#       extract: True
+#       order_enabled: true
+#     products:
+#       # datasets list http://kapadia.github.io/usgs/_sources/reference/catalog/ee.txt may be outdated
+#       # see also https://dds.cr.usgs.gov/ee-data/coveragemaps/shp/ee/
+#       LANDSAT_C2L1:
+#         dataset: landsat_ot_c2_l1
+#         outputs_extension: .tar.gz
+#       LANDSAT_C2L2:
+#         dataset: landsat_ot_c2_l2
+#         outputs_extension: .tar.gz
+#       S2_MSI_L1C:
+#         dataset: SENTINEL_2A
+#         outputs_extension: .zip
+#       GENERIC_PRODUCT_TYPE:
+#         dataset: '{productType}'
+
+## @param config [object] EODAG configuration
+## doc: https://eodag.readthedocs.io/en/stable/getting_started_guide/configure.html
+## ref: https://github.com/CS-SI/eodag/blob/master/eodag/resources/user_conf_template.yml
+config:
+  # peps:
+  #   priority: # Lower value means lower priority (Default: 1)
+  #   search:  # Search parameters configuration
+  #   download:
+  #       extract:  # whether to extract the downloaded products, only applies to archived products (true or false, Default: true).
+  #       outputs_prefix: # where to store downloaded products, as an absolute file path (Default: local temporary directory)
+  #       dl_url_params:  # additional parameters to pass over to the download url as an url parameter
+  #       delete_archive: # whether to delete the downloaded archives (true or false, Default: true).
+  #   auth:
+  #       credentials:
+  #           username:
+  #           password:
+  # cop_cds:
+  #     priority: # Lower value means lower priority (Default: 0)
+  #     api:
+  #         outputs_prefix:
+  #         credentials:
+  #             username:
+  #             password:
+  # cop_dataspace:
+  #     priority: # Lower value means lower priority (Default: 0)
+  #     search:   # Search parameters configuration
+  #     download:
+  #         extract:
+  #         outputs_prefix:
+  #     auth:
+  #         credentials:
+  #             username:
+  #             password:
+
+## @param configExistingSecret.name Existing secret name for EODAG config. If this is set, value config will be ignored
+## @param configExistingSecret.key Existing secret key for EODAG config. If this is set, value config will be ignored
+configExistingSecret:
+  name:
+  key:
+
+## Service configuration
+##
+service:
+  ## @param service.type Kubernetes service type
+  ##
+  type: ClusterIP
+  ## @param service.http.enabled Enable http port on service
+  ##
+  http:
+    enabled: true
+  ## @param service.ports.http EODAG Server service HTTP port
+  ##
+  ports:
+    http: 8080
+  ## @param service.nodePorts [object] Specify the nodePort values for the LoadBalancer and NodePort service types.
+  ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport
+  ##
+  nodePorts:
+    http: ""
+  ## @param service.sessionAffinity Control where client requests go, to the same pod or round-robin
+  ## Values: ClientIP or None
+  ## ref: https://kubernetes.io/docs/user-guide/services/
+  ##
+  sessionAffinity: None
+  ## @param service.sessionAffinityConfig Additional settings for the sessionAffinity
+  ## sessionAffinityConfig:
+  ##   clientIP:
+  ##     timeoutSeconds: 300
+  ##
+  sessionAffinityConfig: {}
+  ## @param service.clusterIP EODAG Server service clusterIP IP
+  ## e.g:
+  ## clusterIP: None
+  ##
+  clusterIP: ""
+  ## @param service.loadBalancerIP loadBalancerIP for the SuiteCRM Service (optional, cloud specific)
+  ## ref: https://kubernetes.io/docs/user-guide/services/#type-loadbalancer
+  ##
+  loadBalancerIP: ""
+  ## @param service.loadBalancerSourceRanges Address that are allowed when service is LoadBalancer
+  ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service
+  ## Example:
+  ## loadBalancerSourceRanges:
+  ##   - 10.10.10.0/24
+  ##
+  loadBalancerSourceRanges: []
+  ## @param service.externalTrafficPolicy Enable client source IP preservation
+  ## ref https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip
+  ##
+  externalTrafficPolicy: Cluster
+  ## @param service.annotations Additional custom annotations for EODAG Server service
+  ##
+  annotations: {}
+  ## @param service.extraPorts Extra port to expose on EODAG Server service
+  ##
+  extraPorts: []
+
+## Configure the ingress for the EODAG Server
+## Ref: https://kubernetes.io/docs/user-guide/ingress/
+## @param ingress.enabled Enable the creation of an ingress for the EODAG Server
+## @param ingress.pathType Path type for the EODAG Server ingress
+## @param ingress.apiVersion Ingress API version for the EODAG Server ingress
+## @param ingress.hostname Ingress hostname for the EODAG Server ingress
+## @param ingress.annotations Annotations for the EODAG Server ingress. To enable certificate autogeneration, place here your cert-manager annotations.
+## @param ingress.tls Enable TLS for the EODAG Server ingress
+## @param ingress.extraHosts Extra hosts array for the EODAG Server ingress
+## @param ingress.path Path array for the EODAG Server ingress
+## @param ingress.extraPaths Extra paths for the EODAG Server ingress
+## @param ingress.extraTls Extra TLS configuration for the EODAG Server ingress
+## @param ingress.secrets Secrets array to mount into the Ingress
+## @param ingress.ingressClassName IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+)
+##
+ingress:
+  ## Set to true to enable ingress record generation
+  ##
+  enabled: false
+  ## @param ingress.selfSigned Create a TLS secret for this ingress record using self-signed certificates generated by Helm
+  ##
+  selfSigned: false
+  ## Ingress Path type
+  ##
+  pathType: ImplementationSpecific
+  ## Override API Version (automatically detected if not set)
+  ##
+  apiVersion: ""
+  ## When the ingress is enabled, a host pointing to this will be created
+  ##
+  hostname: eodag.local
+  ## The Path to eodag. You may need to set this to '/*' in order to use this
+  ## with ALB ingress s.
+  ##
+  path: /
+  ## @param ingress.servicePort Backend service port to use
+  ## Default is http. Alternative is https.
+  ##
+  servicePort: http
+  ## For a full list of possible ingress annotations, please see
+  ## ref: https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/nginx-configuration/annotations.md
+  ## Use this parameter to set the required annotations for cert-manager, see
+  ## ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations
+  ##
+  ## e.g:
+  ## annotations:
+  ##   kubernetes.io/ingress.class: nginx
+  ##   cert-manager.io/cluster-issuer: cluster-issuer-name
+  ##
+  annotations: {}
+  ## Enable TLS configuration for the hostname defined at ingress.hostname parameter
+  ## TLS certificates will be retrieved from a TLS secret with name: {{- printf "%s-tls" .Values.ingress.hostname }}
+  ## You can use the ingress.secrets parameter to create this TLS secret or rely on cert-manager to create it
+  ##
+  tls: false
+  ## The list of additional hostnames to be covered with this ingress record.
+  ## Most likely the hostname above will be enough, but in the event more hosts are needed, this is an array
+  extraHosts: []
+  ## - name: eodag.local
+  ##   path: /
+  ##
+  ## Any additional arbitrary paths that may need to be added to the ingress under the main host.
+  ## For example: The ALB ingress  requires a special rule for handling SSL redirection.
+  extraPaths: []
+  ## - path: /*
+  ##   backend:
+  ##     serviceName: ssl-redirect
+  ##     servicePort: use-annotation
+  ##
+  ## The tls configuration for additional hostnames to be covered with this ingress record.
+  ## see: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls
+  extraTls: []
+  ## - hosts:
+  ##     - eodag.local
+  ##   secretName: eodag.local-tls
+  ##
+
+  ## If you're providing your own certificates, please use this to add the certificates as secrets
+  ## key and certificate should start with -----BEGIN CERTIFICATE----- or
+  ## -----BEGIN RSA PRIVATE KEY-----
+  ##
+  ## name should line up with a tlsSecret set further up
+  ## If you're using cert-manager, this is unneeded, as it will create the secret for you if it is not set
+  ##
+  ## It is also possible to create and manage the certificates outside of this helm chart
+  ## Please see README.md for more information
+  ##
+  secrets: []
+  ## - name: eodag.local-tls
+  ##   key:
+  ##   certificate:
+  ##
+
+  ## This is supported in Kubernetes 1.18+ and required if you have more than one IngressClass marked as the default for your cluster .
+  ## ref: https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/
+  ##
+  ingressClassName: ""
+  ## @param ingress.extraRules Additional rules to be covered with this ingress record
+  ## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-rules
+  ## e.g:
+  ## extraRules:
+  ## - host: example.local
+  ##     http:
+  ##       path: /
+  ##       backend:
+  ##         service:
+  ##           name: example-svc
+  ##           port:
+  ##             name: http
+  ##
+  extraRules: []
+
+## ServiceAccount configuration
+##
+serviceAccount:
+  ## @param serviceAccount.create Specifies whether a ServiceAccount should be created
+  ##
+  create: true
+  ## @param serviceAccount.name The name of the ServiceAccount to use.
+  ## If not set and create is true, a name is generated using the common.names.fullname template
+  ##
+  name: ""
+  ## @param serviceAccount.annotations Additional custom annotations for the ServiceAccount
+  ##
+  annotations: {}
+  ## @param serviceAccount.automountServiceAccountToken Automount service account token for the server service account
+  ##
+  automountServiceAccountToken: true
diff --git a/eodag/api/product/metadata_mapping.py b/eodag/api/product/metadata_mapping.py
index a2525a65..84221d07 100644
--- a/eodag/api/product/metadata_mapping.py
+++ b/eodag/api/product/metadata_mapping.py
@@ -675,7 +675,26 @@ def properties_from_xml(
                 path_or_text,
                 namespaces={k or empty_ns_prefix: v for k, v in root.nsmap.items()},
             )
-            if len(extracted_value) == 1:
+            if len(extracted_value) <= 1:
+                if len(extracted_value) < 1:
+                    # If there is no matched value (empty list), mark the metadata as not
+                    # available
+                    extracted_value = [NOT_AVAILABLE]
+                else:
+                    # store element tag in used_xpaths
+                    used_xpaths.append(
+                        getattr(
+                            root.xpath(
+                                path_or_text.replace("/text()", ""),
+                                namespaces={
+                                    k or empty_ns_prefix: v
+                                    for k, v in root.nsmap.items()
+                                },
+                            )[0],
+                            "tag",
+                            None,
+                        )
+                    )
                 if conversion_or_none is None:
                     properties[metadata] = extracted_value[0]
                 else:
@@ -695,22 +714,10 @@ def properties_from_xml(
                         "{%s%s%s}" % (metadata, SEP, conversion_or_none),
                         **{metadata: extracted_value[0]},
                     )
-                # store element tag in used_xpaths
-                used_xpaths.append(
-                    getattr(
-                        root.xpath(
-                            path_or_text.replace("/text()", ""),
-                            namespaces={
-                                k or empty_ns_prefix: v for k, v in root.nsmap.items()
-                            },
-                        )[0],
-                        "tag",
-                        None,
-                    )
-                )
+
             # If there are multiple matches, consider the result as a list, doing a
             # formatting if any
-            elif len(extracted_value) > 1:
+            else:
                 if conversion_or_none is None:
                     properties[metadata] = extracted_value
                 else:
@@ -743,10 +750,7 @@ def properties_from_xml(
                         )
                         for extracted_value_item in extracted_value
                     ]
-            # If there is no matched value (empty list), mark the metadata as not
-            # available
-            else:
-                properties[metadata] = NOT_AVAILABLE
+
         except XPathEvalError:
             # Assume the mapping is to be passed as is, in which case we readily
             # register it, or is a template, in which case we register it for later
diff --git a/eodag/plugins/download/http.py b/eodag/plugins/download/http.py
index ff52491e..4a5a20ab 100644
--- a/eodag/plugins/download/http.py
+++ b/eodag/plugins/download/http.py
@@ -46,6 +46,7 @@ from eodag.utils import (
     flatten_top_directories,
     parse_header,
     path_to_uri,
+    sanitize,
     uri_to_path,
 )
 from eodag.utils.exceptions import (
@@ -643,7 +644,13 @@ class HTTPDownload(Download):
                         logger.warning("Skipping %s" % asset["href"])
                     error_messages.add(str(e))
                 else:
-                    asset_rel_path = urlparse(asset["href"]).path.strip("/")
+                    asset_rel_path_parts = (
+                        urlparse(asset["href"]).path.strip("/").split("/")
+                    )
+                    asset_rel_path_parts_sanitized = [
+                        sanitize(part) for part in asset_rel_path_parts
+                    ]
+                    asset_rel_path = os.path.join(*asset_rel_path_parts_sanitized)
                     asset_rel_dir = os.path.dirname(asset_rel_path)
 
                     if not asset.get("filename", None):
diff --git a/eodag/resources/providers.yml b/eodag/resources/providers.yml
index f124bba3..cfd19e15 100644
--- a/eodag/resources/providers.yml
+++ b/eodag/resources/providers.yml
@@ -1355,7 +1355,7 @@
       # is obtained from the provider or during the eodag download phase)
       downloadLink: 'ns:link[@rel="enclosure"]/@href'
       # storageStatus: must be one of ONLINE, STAGING, OFFLINE
-      storageStatus: 'DIAS:onlineStatus/text()'
+      storageStatus: '{DIAS:onlineStatus/text()#replace_str("Not Available","OFFLINE")}'
       # order link formated for POST request usage
       orderLink: 'https://apis.mundiwebservices.com/odrapi/0.1/request?{{"productId":"{id}","collectionId":"{platform}"}}'
       searchLink: 'https://{platform}.browse.catalog.mundiwebservices.com/opensearch?uid={id}'
diff --git a/eodag/rest/server.py b/eodag/rest/server.py
index 026635be..fc40a8b6 100755
--- a/eodag/rest/server.py
+++ b/eodag/rest/server.py
@@ -22,6 +22,7 @@ import traceback
 from contextlib import asynccontextmanager
 from distutils import dist
 from json.decoder import JSONDecodeError
+from typing import List, Union
 
 import pkg_resources
 from fastapi import APIRouter as FastAPIRouter
@@ -31,6 +32,7 @@ from fastapi.middleware.cors import CORSMiddleware
 from fastapi.openapi.utils import get_openapi
 from fastapi.responses import FileResponse, ORJSONResponse
 from fastapi.types import Any, Callable, DecoratedCallable
+from pydantic import BaseModel
 from starlette.exceptions import HTTPException as StarletteHTTPException
 
 from eodag.config import load_stac_api_config
@@ -271,16 +273,31 @@ def stac_extension_oseo(request: Request):
     return jsonable_encoder(response)
 
 
+class SearchBody(BaseModel):
+    """
+    class which describes the body of a search request
+    """
+
+    provider: Union[str, None] = None
+    collections: Union[List[str], str]
+    datetime: Union[str, None] = None
+    bbox: Union[list, str, None] = None
+    limit: Union[int, None] = 20
+    page: Union[int, None] = 1
+    query: Union[dict, None] = None
+
+
 @router.get("/search", tags=["STAC"])
 @router.post("/search", tags=["STAC"])
-async def stac_search(request: Request):
+def stac_search(request: Request, search_body: SearchBody = None):
     """STAC collections items"""
     url = request.state.url
     url_root = request.state.url_root
-    try:
-        body = await request.json()
-    except JSONDecodeError:
+
+    if search_body is None:
         body = {}
+    else:
+        body = vars(search_body)
 
     arguments = dict(request.query_params, **body)
     provider = arguments.pop("provider", None)
@@ -288,7 +305,6 @@ async def stac_search(request: Request):
     response = search_stac_items(
         url=url, arguments=arguments, root=url_root, provider=provider
     )
-
     resp = ORJSONResponse(
         content=response, status_code=200, media_type="application/json"
     )
@@ -296,17 +312,15 @@ async def stac_search(request: Request):
 
 
 @router.get("/collections", tags=["Capabilities"])
-async def collections(request: Request):
+def collections(request: Request):
     """STAC collections
 
     Can be filtered using parameters: instrument, platform, platformSerialIdentifier, sensorType, processingLevel
     """
     url = request.state.url
     url_root = request.state.url_root
-    try:
-        body = await request.json()
-    except JSONDecodeError:
-        body = {}
+
+    body = {}
     arguments = dict(request.query_params, **body)
     provider = arguments.pop("provider", None)
 
@@ -316,19 +330,16 @@ async def collections(request: Request):
         arguments=arguments,
         provider=provider,
     )
-
     return jsonable_encoder(response)
 
 
 @router.get("/collections/{collection_id}/items", tags=["Data"])
-async def stac_collections_items(collection_id, request: Request):
+def stac_collections_items(collection_id, request: Request):
     """STAC collections items"""
     url = request.state.url
     url_root = request.state.url_root
-    try:
-        body = await request.json()
-    except JSONDecodeError:
-        body = {}
+
+    body = {}
     arguments = dict(request.query_params, **body)
     provider = arguments.pop("provider", None)
 
@@ -339,19 +350,16 @@ async def stac_collections_items(collection_id, request: Request):
         provider=provider,
         catalogs=[collection_id],
     )
-
     return jsonable_encoder(response)
 
 
 @router.get("/collections/{collection_id}", tags=["Capabilities"])
-async def collection_by_id(collection_id, request: Request):
+def collection_by_id(collection_id, request: Request):
     """STAC collection by id"""
     url = request.state.url
     url_root = request.state.url_root
-    try:
-        body = await request.json()
-    except JSONDecodeError:
-        body = {}
+
+    body = {}
     arguments = dict(request.query_params, **body)
     provider = arguments.pop("provider", None)
 
@@ -370,10 +378,8 @@ async def stac_collections_item(collection_id, item_id, request: Request):
     """STAC collection item by id"""
     url = request.state.url
     url_root = request.state.url_root
-    try:
-        body = await request.json()
-    except JSONDecodeError:
-        body = {}
+
+    body = {}
     arguments = dict(request.query_params, **body)
     provider = arguments.pop("provider", None)
 
@@ -397,12 +403,10 @@ async def stac_collections_item(collection_id, item_id, request: Request):
 
 
 @router.get("/collections/{collection_id}/items/{item_id}/download", tags=["Data"])
-async def stac_collections_item_download(collection_id, item_id, request: Request):
+def stac_collections_item_download(collection_id, item_id, request: Request):
     """STAC collection item local download"""
-    try:
-        body = await request.json()
-    except JSONDecodeError:
-        body = {}
+
+    body = {}
     arguments = dict(request.query_params, **body)
     provider = arguments.pop("provider", None)
 
diff --git a/eodag/rest/utils.py b/eodag/rest/utils.py
index c424a647..de5d168a 100644
--- a/eodag/rest/utils.py
+++ b/eodag/rest/utils.py
@@ -232,7 +232,9 @@ def get_geometry(arguments):
 
     geom = None
 
-    if "bbox" in arguments or "box" in arguments:
+    if ("bbox" in arguments and arguments["bbox"] is not None) or (
+        "box" in arguments and arguments["box"] is not None
+    ):
         # get bbox
         request_bbox = arguments.pop("bbox", None) or arguments.pop("box", None)
         if request_bbox and isinstance(request_bbox, str):
@@ -688,7 +690,7 @@ def search_stac_items(url, arguments, root="/", catalogs=[], provider=None):
         arguments.pop("ids")
 
     # get datetime
-    if "datetime" in arguments.keys():
+    if "datetime" in arguments.keys() and arguments["datetime"] is not None:
         dtime_split = arguments.get("datetime", "").split("/")
         if len(dtime_split) > 1:
             arguments["dtstart"] = (


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d5da06b7..ff179549 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -146,8 +146,8 @@ jobs:
         key: ${{ runner.os }}-pip-${{ steps.get-week.outputs.WEEK }}-${{ hashFiles('setup.cfg') }}
     - name: Install pandoc
       run: |
-        sudo apt update
-        sudo apt install -y make pandoc
+        curl -s https://api.github.com/repos/jgm/pandoc/releases/latest | grep -o "https.*amd64.deb" | wget -O pandoc.deb -qi -
+        sudo dpkg -i pandoc.deb && rm pandoc.deb
     - name: Install tox
       run: |
         python -m pip install tox
diff --git a/tests/units/test_download_plugins.py b/tests/units/test_download_plugins.py
index 902a8e56..0755ad55 100644
--- a/tests/units/test_download_plugins.py
+++ b/tests/units/test_download_plugins.py
@@ -182,7 +182,7 @@ class TestDownloadPluginHttp(BaseDownloadPluginTest):
         self.product.location = self.product.remote_location = "http://somewhere"
         self.product.properties["id"] = "someproduct"
         self.product.assets = {
-            "foo": {"href": "http://somewhere/something?foo=bar#baz"}
+            "foo": {"href": "http://somewhere/mal:for;matted/something?foo=bar#baz"}
         }
         mock_requests_get.return_value.__enter__.return_value.headers = {
             "content-disposition": ""
@@ -194,7 +194,11 @@ class TestDownloadPluginHttp(BaseDownloadPluginTest):
         self.assertEqual(path, os.path.join(self.output_dir, "dummy_product"))
         self.assertTrue(os.path.isdir(path))
         self.assertTrue(
-            os.path.isfile(os.path.join(self.output_dir, "dummy_product", "something"))
+            os.path.isfile(
+                os.path.join(
+                    self.output_dir, "dummy_product", "mal_for_matted", "something"
+                )
+            )
         )
 
         # Check if the GET request has been called for both size request and download request