diff --git a/.containerignore b/.containerignore
index 5c05940..db4bae9 100644
--- a/.containerignore
+++ b/.containerignore
@@ -1,5 +1,4 @@
 .venv
-.git
 dist
 .pytest_cache
 .mypy_cache
diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml
index 14bc5f3..63f256a 100644
--- a/.forgejo/workflows/build.yaml
+++ b/.forgejo/workflows/build.yaml
@@ -30,6 +30,8 @@ jobs:
           echo '${{ toJSON(env) }}'
 
       - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
 
       - name: Install app with 'uv pip install'
         run: uv pip install --python=$UV_PROJECT_ENVIRONMENT --no-deps .
@@ -48,40 +50,35 @@ jobs:
         with:
           fetch-depth: 0
 
-      - name: Get the version from git
-        id: version
-        run: echo "version=$(git describe --dirty --tags)" >> $GITHUB_OUTPUT
+      - name: Install the latest version of uv
+        uses: astral-sh/setup-uv@v4
+        with:
+          version: "0.6.6"
 
-      - name: Check if the container should be built
-        id: builder
-        env:
-          RUN: ${{ toJSON(inputs.build || !contains(steps.version.outputs.version, '-g')) }}
-        run: |
-          echo "run=$RUN" >> $GITHUB_OUTPUT
-          echo "Run build: $RUN"
+      - name: Install
+        run: uv sync
 
-      - name: Info - version and test if the git version is clean (then python package and image container should be built)
-        env:
-          VERSION: ${{ steps.version.outputs.version }}
-          RUN: ${{ steps.builder.outputs.run }}
-          FORCE: ${{ toJSON(inputs.build) }}
-        run: |
-          echo "Version $VERSION, force (manual input): $FORCE, run the build: $RUN"
 
-      - name: Set the version in pyproject.toml (workaround for uv not supporting dynamic version)
-        if: fromJSON(steps.builder.outputs.run)
-        env:
-          VERSION: ${{ steps.version.outputs.version }}
-        run: sed "s/0.0.0/${VERSION}/" -i pyproject.toml
+      - name: Get version
+        run: echo "VERSION=$(.venv/bin/dunamai from any --style semver)" >> $GITHUB_ENV
+
+      - name: Version
+        run: echo $VERSION
+
+      - name: Get distance from tag
+        run: echo "DISTANCE=$(.venv/bin/dunamai from any --format '{distance}')" >> $GITHUB_ENV
+
+      - name: Distance
+        run: echo $DISTANCE
 
       - name: Workaround for bug of podman-login
-        if: fromJSON(steps.builder.outputs.run)
+        if: env.DISTANCE == '0'
         run: |
           mkdir -p $HOME/.docker
           echo "{ \"auths\": {} }" > $HOME/.docker/config.json
 
       - name: Log in to the container registry (with another workaround)
-        if: fromJSON(steps.builder.outputs.run)
+        if: env.DISTANCE == '0'
         uses: actions/podman-login@v1
         with:
           registry: ${{ vars.REGISTRY }}
@@ -90,37 +87,31 @@ jobs:
           auth_file_path: /tmp/auth.json
 
       - name: Build the container image
-        if: fromJSON(steps.builder.outputs.run)
+        if: env.DISTANCE == '0'
         uses: actions/buildah-build@v1
         with:
           image: gisaf-backend
           oci: true
           labels: gisaf-backend
-          tags: latest ${{ steps.version.outputs.version }}
+          tags: "latest ${{ env.VERSION }}"
           containerfiles: |
             ./Containerfile
-          build-args: |
-            APP_VERSION=${{ steps.version.outputs.version }}
 
       - name: Push the image to the registry
-        if: fromJSON(steps.builder.outputs.run)
+        if: env.DISTANCE == '0'
         uses: actions/push-to-registry@v2
         with:
           registry: "docker://${{ vars.REGISTRY }}/${{ vars.ORGANISATION }}"
           image: gisaf-backend
-          tags: latest ${{ steps.version.outputs.version }}
+          tags: "latest ${{ env.VERSION }}"
 
-      - name: Install uv
-        uses: astral-sh/setup-uv@v4
-        with:
-          version: "0.5.9"
-
-      - name: Build python package
-        if: fromJSON(steps.builder.outputs.run)
+      - name: Build wheel
+        if: env.DISTANCE == '0'
         run: uv build --wheel
 
       - name: Publish Python package (home)
-        if: fromJSON(steps.builder.outputs.run)
+        if: env.DISTANCE == '0'
         env:
           LOCAL_PYPI_TOKEN: ${{ secrets.LOCAL_PYPI_TOKEN }}
         run: uv publish --publish-url https://code.philo.ydns.eu/api/packages/philorg/pypi --token $LOCAL_PYPI_TOKEN
+        continue-on-error: true
diff --git a/Containerfile b/Containerfile
index 18b0cfb..03508c5 100644
--- a/Containerfile
+++ b/Containerfile
@@ -1,19 +1,17 @@
 # Build: podman build -t code.philo.ydns.eu/philorg/gisaf-backend -f Containerfile
 
-FROM code.philo.ydns.eu/philorg/gisaf-backend-deps
+FROM docker.io/library/python:latest
 
-ENV PYTHONPATH $UV_PROJECT_ENVIRONMENT/lib/python3.12/site-packages
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
 ENV GISAF__BACKEND__PORT 8898
 ENV GISAF__BACKEND__BIND__ADDR 0.0.0.0
-ARG APP_VERSION=0.0.0
 
-COPY . /src
+COPY . /app
 
-RUN uv pip install \
-  --python=$UV_PROJECT_ENVIRONMENT \
-  --no-deps \
-  /src
+# Sync the project into a new environment, using the frozen lockfile
+WORKDIR /app
+
+RUN uv pip install --system .
 
-RUN echo $APP_VERSION > /app/version.txt
 
 CMD uvicorn gisaf.application:app --port ${GISAF__BACKEND__PORT} --host ${GISAF__BACKEND__BIND__ADDR}
diff --git a/README.md b/README.md
index 8f760bf..133a663 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,59 @@
 # Gisaf
+
+*Gisaf* is a web based GIS (Geographical Information System) initially developed
+for the CSR Geomatics unit in Auroville, India.
+
+Its main audiences are local administrations which need a platform
+to develop and maintain a geographical, topological and environmental data,
+allow teams of surveyors to upload data and update the GIS in near real time,
+and collect data from a wide range of sources.
+
+Although it just works out of the box, it is primarily intended to be customized
+and extended with the data, uses, needs.
+
+## Features
+
+- Layers defined with Python plugins or ANSI-standard categories
+- Open source industry standard interfaces (PostGIS database, OGCAPI)
+- Support of different geographical projections
+- Integrated administration interface
+- Export of data from standard formats (Geopackage, Shapefile)
+- Import and update of data managed in a well defined workflow with "baskets"
+- Acquisition of different kinds of surveyors' equipments, accuracies
+- Robust mechanisms of identification of the data sources, survey times
+- Geographical feature statuses (eg. Existing, Deprecated, Future)
+- Role-based access to data
+- Support temporal and other data for different features
+- Easy integration with IoT sensors, eg. using the MQTT protocol
+- Detailed information for all features
+- Tagging of features
+- Customizable, user triggered actions
+- Customizable background maps
+- Customizable dashboards
+- Customizable base maps (domain specific set of layers)
+- Python and Jupyter integration: easy to interact with, add features, components,
+  explore data and conduct scientific research 
+- Plugin architecture: add functions with Python packages
+- Free and open source
+
+## Software stack
+
+- Python: the Swiss Army knife language
+- Pydantic: the power of typing
+- FastAPI: web wizzard
+- SQLModel: SQL (ORM)
+- Postgis: geographical extension for the PostGresql database
+- Pandas and Geopandas: the data jugglers
+
+## Installation / deployment
+
+See the documentation in the deployment directory.
+
+## Configuration
+
+Documentation is being rewritten. Please contact us.
+
+## Demo
+
+A very basic demo without any data, basically to give a feel of the user interface,
+is available [here](https://gisaf.philo.ydns.eu).
diff --git a/deployment/README.md b/deployment/README.md
new file mode 100644
index 0000000..966bdde
--- /dev/null
+++ b/deployment/README.md
@@ -0,0 +1,105 @@
+# Gisaf deployment / installation
+
+This documentation covers the specifics of Gisaf deployment, but
+it does no cover application configuration details,
+the set up of a public facing web server handling dns domain / https security.
+
+*Gisaf* is shipped as containers for easy and effective installation,
+and packages to deploy them for different environments.
+
+There are 4 containers:
+
+- Frontend (Angular build: static files, served by an nginx process,
+  eventually forwarding requests to the backend)
+- Backend (Python server)
+- Postgis database server
+- Redis server
+
+Commands below assume that they are run from their respective directories
+(`deployment/systemd`, `deployment/kubernetes`, ...).
+
+## Systemd
+
+With the help of *podman*, systemd can handle starting/restarting services, etc,
+even with a non-privileged user.
+
+The 4 containers are in the same *pod*, as it is meant primarily for a lightweight
+production environment, but this can be easily adapted.
+
+The `deployment/systemd` directory contains files used for running *Gisaf* with Systemd.
+Podman is used to facilitate the creation of the relevant services.
+
+### Deployment
+
+```sh
+cp -r * $HOME/.config/containers/systemd/
+systemctl --user daemon-reload
+systemctl --user start gisaf-pod.service
+```
+
+Environment variables for the Gisaf configuration can be added
+in the `gisaf-backend.container` file.
+
+Note that starting on system boot requires the user to be enabled accordingly with:
+
+```sh
+sudo loginctl enable-linger <username>
+```
+
+## Plain Kubernetes (no Helm)
+
+The `kubernetes` directory contains files for deployment on Kubernetes.
+
+The standard installation uses a namespace named `gisaf`.
+
+The Kubernetes configuration is split in 2 files: `gisaf.yaml` and `config.yaml`
+
+Deployment:
+
+```sh
+kubectl create namespace gisaf
+kubectl apply -f config.yaml
+kubectl apply -f gisaf.yaml
+```
+
+Update after modification on the server (frontend and backend):
+
+```sh
+kubectl --namespace gisaf rollout restart deployment gisaf-server-deployment
+```
+
+## Helm
+
+The `helm` chart is in the directory named `helm`.
+
+### Deploy on Kubernetes
+
+Deploying with Helm on Kubernetes makes it straightforward to
+run on cloud services.
+
+```sh
+kubectl create namespace gisaf
+helm install gisaf helm
+```
+
+#### Update
+
+```sh
+helm upgrade gisaf helm
+```
+
+### Publish the Helm chart
+
+First, build the Helm package:
+
+```sh
+helm package helm
+```
+
+Then upload it:
+
+```sh
+helm --user phil:<password> -X POST \
+--upload-file gisaf-0.1.0.tgz \
+https://code.philo.ydns.eu/api/packages/philorg/helm/api/charts
+```
diff --git a/deployment/kubernetes/.gitignore b/deployment/kubernetes/.gitignore
new file mode 100644
index 0000000..68242e7
--- /dev/null
+++ b/deployment/kubernetes/.gitignore
@@ -0,0 +1 @@
+gisaf-*.tgz
diff --git a/deployment/kubernetes/config.yaml b/deployment/kubernetes/config.yaml
new file mode 100644
index 0000000..5bbfa78
--- /dev/null
+++ b/deployment/kubernetes/config.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: gisaf-config
+  namespace: gisaf
+data:
+  GISAF__MAP__LNG: "6.178"
+  GISAF__MAP__LAT: "45.8818"
+  GISAF__MAP__ZOOM: "14"
+  GISAF__MAP__PITCH: "40"
diff --git a/deployment/kubernetes/gisaf.yaml b/deployment/kubernetes/gisaf.yaml
index 50eca18..9932322 100644
--- a/deployment/kubernetes/gisaf.yaml
+++ b/deployment/kubernetes/gisaf.yaml
@@ -2,6 +2,7 @@ apiVersion: v1
 kind: Service
 metadata:
   name: gisaf-database
+  namespace: gisaf
   labels:
     app: gisaf-database
 spec:
@@ -14,56 +15,66 @@ spec:
     app: gisaf-database
 
 ---
-apiVersion: v1
-kind: Pod
+apiVersion: apps/v1
+kind: Deployment
 metadata:
-  name: gisaf-database
-  annotations:
-    io.kubernetes.cri-o.SandboxID/gisaf-database: gisaf-cri-o
-    io.kubernetes.cri-o.SandboxID/gisaf-redis: gisaf-cri-o
-    io.podman.annotations.infra.name: gisaf-infra
+  name: gisaf-database-deployment
+  namespace: gisaf
   labels:
     app: gisaf-database
 spec:
-  hostAliases:
-    - ip: "127.0.0.1"
-      hostnames:
-        - "gisaf-redis"
-        - "gisaf-database"
-  containers:
-    - name: gisaf-database
-      image: code.philo.ydns.eu/philorg/gisaf-database:latest
-      args:
-        - postgres
-      volumeMounts:
-        - mountPath: /var/lib/postgresql/data
-          name: gisaf-pgdata
-      ports:
-        - containerPort: 5432
-          name: psql
-    - image: docker.io/library/redis:alpine
-      name: gisaf-redis
-      args:
-        - redis-server
-      volumeMounts:
-        - mountPath: /data
+  replicas: 1
+  selector:
+    matchLabels:
+      app: gisaf-database
+  template:
+    metadata:
+      namespace: gisaf
+      labels:
+        app: gisaf-database
+    spec:
+      hostAliases:
+        - ip: "127.0.0.1"
+          hostnames:
+            - "gisaf-redis"
+            - "gisaf-database"
+      containers:
+        - name: gisaf-database
+          image: code.philo.ydns.eu/philorg/gisaf-database:latest
+          imagePullPolicy: Always
+          args:
+            - postgres
+          volumeMounts:
+            - mountPath: /var/lib/postgresql/data
+              name: gisaf-pgdata
+          ports:
+            - containerPort: 5432
+              name: psql
+        - image: docker.io/library/redis:alpine
+          imagePullPolicy: Always
           name: gisaf-redis
-      ports:
-        - containerPort: 6379
-          name: redis
-  volumes:
-    - name: gisaf-pgdata
-      persistentVolumeClaim:
-        claimName: gisaf-pgdata-pvc
-    - name: gisaf-redis
-      persistentVolumeClaim:
-        claimName: gisaf-redis-pvc
+          args:
+            - redis-server
+          volumeMounts:
+            - mountPath: /data
+              name: gisaf-redis
+          ports:
+            - containerPort: 6379
+              name: redis
+      volumes:
+        - name: gisaf-pgdata
+          persistentVolumeClaim:
+            claimName: gisaf-pgdata-pvc
+        - name: gisaf-redis
+          persistentVolumeClaim:
+            claimName: gisaf-redis-pvc
 
 ---
 apiVersion: v1
 kind: Service
 metadata:
   name: gisaf-server
+  namespace: gisaf
   labels:
     app: gisaf-server
 spec:
@@ -76,57 +87,71 @@ spec:
   #type: NodePort
 
 ---
-apiVersion: v1
-kind: Pod
+apiVersion: apps/v1
+kind: Deployment
 metadata:
-  name: gisaf-server
-  annotations:
-    io.kubernetes.cri-o.SandboxID/gisaf-backend: gisaf-cri-o
-    io.kubernetes.cri-o.SandboxID/gisaf-frontend: gisaf-cri-o
-    io.podman.annotations.infra.name: gisaf-infra
+  name: gisaf-server-deployment
+  namespace: gisaf
   labels:
     app: gisaf-server
 spec:
-  hostAliases:
-    - ip: "127.0.0.1"
-      hostnames:
-        - "gisaf-frontend"
-        - "gisaf-backend"
-  initContainers:
-    - name: gisaf-backend-initdb
-      image: code.philo.ydns.eu/philorg/gisaf-backend:latest
-      command: ["gisaf", "create-db"]
-      env:
-        - name: GISAF__DB__HOST
-          value: gisaf-database
-  containers:
-    - name: gisaf-backend
-      image: code.philo.ydns.eu/philorg/gisaf-backend:latest
-      env:
-        - name: GISAF__GISAF_LIVE__REDIS
-          value: redis://gisaf-database
-        - name: GISAF__DB__HOST
-          value: gisaf-database
-    - name: gisaf-frontend
-      image: code.philo.ydns.eu/philorg/gisaf-frontend:latest
-      args:
-        - nginx
-        - -g
-        - daemon off;
-      ports:
-        - containerPort: 80
-          hostPort: 8899
+  replicas: 2
+  selector:
+    matchLabels:
+      app: gisaf-server
+  template:
+    metadata:
+      namespace: gisaf
+      labels:
+        app: gisaf-server
+    spec:
+      hostAliases:
+        - ip: "127.0.0.1"
+          hostnames:
+            - "gisaf-frontend"
+            - "gisaf-backend"
+      initContainers:
+        - name: gisaf-backend-initdb
+          image: code.philo.ydns.eu/philorg/gisaf-backend:0.5.0-alpha.10
+          imagePullPolicy: Always
+          command: ["gisaf", "create-db"]
+          env:
+            - name: GISAF__DB__HOST
+              value: gisaf-database
+      containers:
+        - name: gisaf-backend
+          image: code.philo.ydns.eu/philorg/gisaf-backend:0.5.0-alpha.10
+          imagePullPolicy: Always
+          env:
+            - name: GISAF__GISAF_LIVE__REDIS
+              value: redis://gisaf-database
+            - name: GISAF__DB__HOST
+              value: gisaf-database
+          envFrom:
+            - configMapRef:
+                name: gisaf-config
+        - name: gisaf-frontend
+          image: code.philo.ydns.eu/philorg/gisaf-frontend:0.5.0-alpha.13
+          imagePullPolicy: Always
+          args:
+            - nginx
+            - -g
+            - daemon off;
+          ports:
+            - containerPort: 80
+              #hostPort: 8899
 
 ---
 apiVersion: v1
 kind: PersistentVolume
 metadata:
   name: gisaf-pgdata-pv
+  namespace: gisaf
   labels:
     type: local
     app: gisaf-postgres
 spec:
-  storageClassName: manual
+  storageClassName: local-path
   capacity:
     storage: 2Gi
   accessModes:
@@ -139,11 +164,12 @@ apiVersion: v1
 kind: PersistentVolume
 metadata:
   name: gisaf-redis-pv
+  namespace: gisaf
   labels:
     type: local
     app: gisaf-redis
 spec:
-  storageClassName: manual
+  storageClassName: local-path
   capacity:
     storage: 2Gi
   accessModes:
@@ -156,8 +182,9 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: gisaf-pgdata-pvc
+  namespace: gisaf
 spec:
-  storageClassName: manual
+  storageClassName: local-path
   accessModes:
     - ReadWriteMany
   resources:
@@ -169,8 +196,9 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: gisaf-redis-pvc
+  namespace: gisaf
 spec:
-  storageClassName: manual
+  storageClassName: local-path
   accessModes:
     - ReadWriteMany
   resources:
@@ -182,7 +210,7 @@ apiVersion: networking.k8s.io/v1
 kind: Ingress
 metadata:
   name: gisaf
-  namespace: default
+  namespace: gisaf
   #annotations:
   #  kubernetes.io/ingress.class: traefik
   #  #traefik.ingress.kubernetes.io/router.middlewares: default-strip-prefix@kubernetescrd
diff --git a/deployment/kubernetes/helm/.helmignore b/deployment/kubernetes/helm/.helmignore
new file mode 100644
index 0000000..0e8a0eb
--- /dev/null
+++ b/deployment/kubernetes/helm/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/deployment/kubernetes/helm/Chart.yaml b/deployment/kubernetes/helm/Chart.yaml
new file mode 100644
index 0000000..f995115
--- /dev/null
+++ b/deployment/kubernetes/helm/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v2
+name: gisaf
+description: Gisaf Helm chart for Kubernetes
+type: application
+version: 0.1.0
+appVersion: "0.15.0-alpha"
diff --git a/deployment/kubernetes/helm/templates/config.yaml b/deployment/kubernetes/helm/templates/config.yaml
new file mode 100644
index 0000000..0f6165e
--- /dev/null
+++ b/deployment/kubernetes/helm/templates/config.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ .Release.Name }}-config
+  namespace: gisaf
+data:
+  GISAF__MAP__LNG: "{{ .Values.map.lng }}"
+  GISAF__MAP__LAT: "{{ .Values.map.lat }}"
+  GISAF__MAP__ZOOM: "{{ .Values.map.zoom }}"
+  GISAF__MAP__PITCH: "{{ .Values.map.pitch }}"
diff --git a/deployment/kubernetes/helm/templates/database.yaml b/deployment/kubernetes/helm/templates/database.yaml
new file mode 100644
index 0000000..d9ad4f7
--- /dev/null
+++ b/deployment/kubernetes/helm/templates/database.yaml
@@ -0,0 +1,135 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: gisaf-database
+  namespace: gisaf
+  labels:
+    app: gisaf-database
+spec:
+  ports:
+    - name: psql
+      port: 5432
+    - name: redis
+      port: 6379
+  selector:
+    app: gisaf-database
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: gisaf-database-deployment
+  namespace: gisaf
+  labels:
+    app: gisaf-database
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: gisaf-database
+  template:
+    metadata:
+      namespace: gisaf
+      labels:
+        app: gisaf-database
+    spec:
+      hostAliases:
+        - ip: "127.0.0.1"
+          hostnames:
+            - "gisaf-redis"
+            - "gisaf-database"
+      containers:
+        - name: gisaf-database
+          image: code.philo.ydns.eu/philorg/gisaf-database:latest
+          imagePullPolicy: Always
+          args:
+            - postgres
+          volumeMounts:
+            - mountPath: /var/lib/postgresql/data
+              name: gisaf-pgdata
+          ports:
+            - containerPort: 5432
+              name: psql
+        - image: docker.io/library/redis:alpine
+          imagePullPolicy: Always
+          name: gisaf-redis
+          args:
+            - redis-server
+          volumeMounts:
+            - mountPath: /data
+              name: gisaf-redis
+          ports:
+            - containerPort: 6379
+              name: redis
+      volumes:
+        - name: gisaf-pgdata
+          persistentVolumeClaim:
+            claimName: gisaf-pgdata-pvc
+        - name: gisaf-redis
+          persistentVolumeClaim:
+            claimName: gisaf-redis-pvc
+
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: gisaf-pgdata-pvc
+  namespace: gisaf
+spec:
+  storageClassName: local-path
+  accessModes:
+    - ReadWriteMany
+  resources:
+    requests:
+      storage: 2Gi
+
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: gisaf-redis-pvc
+  namespace: gisaf
+spec:
+  storageClassName: local-path
+  accessModes:
+    - ReadWriteMany
+  resources:
+    requests:
+      storage: 200Mi
+
+---
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+  name: gisaf-pgdata-pv
+  namespace: gisaf
+  labels:
+    type: local
+    app: gisaf-postgres
+spec:
+  storageClassName: local-path
+  capacity:
+    storage: 2Gi
+  accessModes:
+    - ReadWriteMany
+  hostPath:
+    path: /data/gisaf/postgresql
+
+---
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+  name: gisaf-redis-pv
+  namespace: gisaf
+  labels:
+    type: local
+    app: gisaf-redis
+spec:
+  storageClassName: local-path
+  capacity:
+    storage: 2Gi
+  accessModes:
+    - ReadWriteMany
+  hostPath:
+    path: /data/gisaf/redis
diff --git a/deployment/kubernetes/helm/templates/network.yaml b/deployment/kubernetes/helm/templates/network.yaml
new file mode 100644
index 0000000..d02a579
--- /dev/null
+++ b/deployment/kubernetes/helm/templates/network.yaml
@@ -0,0 +1,28 @@
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: gisaf
+  namespace: gisaf
+  #annotations:
+  #  kubernetes.io/ingress.class: traefik
+  #  #traefik.ingress.kubernetes.io/router.middlewares: default-strip-prefix@kubernetescrd
+spec:
+  rules:
+    - http:
+        paths:
+          - path: /
+            pathType: Prefix
+            backend:
+              service:
+                name: gisaf-server
+                port:
+                  number: 80
+          - path: /api
+            pathType: Prefix
+            backend:
+              service:
+                name: gisaf-server
+                port:
+                  number: 8081
+
diff --git a/deployment/kubernetes/helm/templates/server.yaml b/deployment/kubernetes/helm/templates/server.yaml
new file mode 100644
index 0000000..2ced596
--- /dev/null
+++ b/deployment/kubernetes/helm/templates/server.yaml
@@ -0,0 +1,69 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: gisaf-server
+  namespace: gisaf
+  labels:
+    app: gisaf-server
+spec:
+  ports:
+    - port: 80
+      targetPort: 80
+  selector:
+    app: gisaf-server
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: gisaf-server-deployment
+  namespace: gisaf
+  labels:
+    app: gisaf-server
+spec:
+  replicas: {{ .Values.replicaCount }}
+  selector:
+    matchLabels:
+      app: gisaf-server
+  template:
+    metadata:
+      namespace: gisaf
+      labels:
+        app: gisaf-server
+    spec:
+      hostAliases:
+        - ip: "127.0.0.1"
+          hostnames:
+            - "gisaf-frontend"
+            - "gisaf-backend"
+      initContainers:
+        - name: gisaf-backend-initdb
+          image: "code.philo.ydns.eu/philorg/gisaf-backend:{{ .Values.version.backend }}"
+          imagePullPolicy: Always
+          command: ["gisaf", "create-db"]
+          env:
+            - name: GISAF__DB__HOST
+              value: gisaf-database
+      containers:
+        - name: gisaf-backend
+          image: "code.philo.ydns.eu/philorg/gisaf-backend:{{ .Values.version.backend }}"
+          imagePullPolicy: Always
+          env:
+            - name: GISAF__GISAF_LIVE__REDIS
+              value: redis://gisaf-database
+            - name: GISAF__DB__HOST
+              value: gisaf-database
+          envFrom:
+            - configMapRef:
+                name: gisaf-config
+        - name: gisaf-frontend
+          image: "code.philo.ydns.eu/philorg/gisaf-frontend:{{ .Values.version.frontend }}"
+          imagePullPolicy: Always
+          args:
+            - nginx
+            - -g
+            - daemon off;
+          ports:
+            - containerPort: 80
+
diff --git a/deployment/kubernetes/helm/values.yaml b/deployment/kubernetes/helm/values.yaml
new file mode 100644
index 0000000..deb2537
--- /dev/null
+++ b/deployment/kubernetes/helm/values.yaml
@@ -0,0 +1,13 @@
+# Replicaset count for the server (2 pods: frontend and backend)
+replicaCount: 2
+
+version:
+  backend: 0.5.0-alpha.10
+  frontend: 0.5.0-alpha.13
+
+# Map config
+map:
+  lng: "6.178"
+  lat: "45.8818"
+  zoom: "14"
+  pitch: "40"
diff --git a/pyproject.toml b/pyproject.toml
index f8ae259..936cb5d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
 [project]
 name = "gisaf-backend"
-version = "0.0.0"
 description = "Gisaf backend"
+dynamic = ["version"]
 authors = [{ name = "phil", email = "phil.dev@philome.mooo.com" }]
 dependencies = [
   "aiopath>=0.7.1",
@@ -40,20 +40,9 @@ readme = "README.md"
 [project.scripts]
 gisaf = "gisaf.cli:cli"
 
-[project.optional-dependencies]
-contextily = ["contextily>=1.4.0"]
-mqtt = ["aiomqtt>=1.2.1"]
-all = ["gisaf-backend[contextily]", "gisaf-backend[mqtt]"]
-
-[build-system]
-requires = ["hatchling"]
-build-backend = "hatchling.build"
-
-[tool.hatch.build.targets.wheel]
-packages = ["src/gisaf"]
-
-[tool.uv]
-dev-dependencies = [
+[dependency-groups]
+dev = [
+  "dunamai>=1.23.0",
   "ipdb>=0.13.13",
   "pandas-stubs>=2.1.4.231218",
   "pretty-errors>=1.2.25",
@@ -64,3 +53,27 @@ dev-dependencies = [
   "types-passlib>=1.7.7.20240311",
   "pytest>=8.3.4",
 ]
+
+[project.optional-dependencies]
+contextily = ["contextily>=1.4.0"]
+mqtt = ["aiomqtt>=1.2.1"]
+all = ["gisaf-backend[contextily]", "gisaf-backend[mqtt]"]
+
+[build-system]
+requires = ["hatchling", "uv-dynamic-versioning"]
+build-backend = "hatchling.build"
+
+[tool.hatch.version]
+source = "uv-dynamic-versioning"
+
+[tool.hatch.build.targets.wheel]
+packages = ["src/gisaf"]
+
+[tool.uv-dynamic-versioning]
+style = "semver"
+
+[tool.uv]
+package = true
+
+[tool.black]
+line-length = 98
diff --git a/src/gisaf/__init__.py b/src/gisaf/__init__.py
index e69de29..710f782 100644
--- a/src/gisaf/__init__.py
+++ b/src/gisaf/__init__.py
@@ -0,0 +1,11 @@
+import importlib.metadata
+
+try:
+    from dunamai import Version, Style
+
+    __version__ = Version.from_git().serialize(style=Style.SemVer, dirty=True)
+except ImportError:
+    # __name__ could be used if the package name is the same
+    # as the directory - not the case here
+    # __version__ = importlib.metadata.version(__name__)
+    __version__ = importlib.metadata.version("gisaf-backend")
diff --git a/src/gisaf/application.py b/src/gisaf/application.py
index fe6cdd5..82dff4d 100644
--- a/src/gisaf/application.py
+++ b/src/gisaf/application.py
@@ -42,6 +42,8 @@ app = FastAPI(
     version=conf.version,
     lifespan=lifespan,
     default_response_class=responses.ORJSONResponse,
+    docs_url="/api/docs",
+    redoc_url="/api/redoc",
 )
 
 app.include_router(api, prefix="/api")
diff --git a/src/gisaf/config.py b/src/gisaf/config.py
index ab771f2..1cdb478 100644
--- a/src/gisaf/config.py
+++ b/src/gisaf/config.py
@@ -3,7 +3,7 @@ import logging
 from pathlib import Path
 from typing import Any, Type, Tuple
 from yaml import safe_load
-from importlib.metadata import version
+from importlib.metadata import version as importlib_version
 
 from xdg import BaseDirectory
 from pydantic import BaseModel
@@ -14,6 +14,9 @@ from pydantic_settings import (
     YamlConfigSettingsSource,
 )
 
+from gisaf import __version__
+
+
 logger = logging.getLogger(__name__)
 ENV = environ.get("env", "prod")
 
@@ -27,12 +30,8 @@ config_files = [
 
 class DashboardHome(BaseModel):
     title: str = "Gisaf - home/dashboards"
-    content_file: Path = (
-        Path(Path.cwd().root) / "etc" / "gisaf" / "dashboard_home_content.html"
-    )
-    footer_file: Path = (
-        Path(Path.cwd().root) / "etc" / "gisaf" / "dashboard_home_footer.html"
-    )
+    content_file: Path = Path(Path.cwd().root) / "etc" / "gisaf" / "dashboard_home_content.html"
+    footer_file: Path = Path(Path.cwd().root) / "etc" / "gisaf" / "dashboard_home_footer.html"
 
 
 class GisafConfig(BaseModel):
@@ -120,7 +119,9 @@ class DB(BaseModel):
     echo: bool = False
 
     def get_sqla_url(self):
-        return f"postgresql+asyncpg://{self.user}:{self.password}@{self.host}:{self.port}/{self.db}"
+        return (
+            f"postgresql+asyncpg://{self.user}:{self.password}@{self.host}:{self.port}/{self.db}"
+        )
 
     def get_pg_url(self):
         return f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.db}"
@@ -202,9 +203,7 @@ class OGCAPI(BaseModel):
 class TileServer(BaseModel):
     baseDir: Path = Path(BaseDirectory.xdg_data_home) / "gisaf" / "mbtiles_files_dir"
     useRequestUrl: bool = False
-    spriteBaseDir: Path = (
-        Path(BaseDirectory.xdg_data_home) / "gisaf" / "mbtiles_sprites_dir"
-    )
+    spriteBaseDir: Path = Path(BaseDirectory.xdg_data_home) / "gisaf" / "mbtiles_sprites_dir"
     spriteUrl: str = "/tiles/sprite/sprite"
     spriteBaseUrl: str = "https://gisaf.example.org"
     openMapTilesKey: str | None = None
@@ -311,9 +310,7 @@ class Config(BaseSettings):
         dotenv_settings: PydanticBaseSettingsSource,
         file_secret_settings: PydanticBaseSettingsSource,
     ) -> Tuple[PydanticBaseSettingsSource, ...]:
-        configs = [
-            YamlConfigSettingsSource(settings_cls, yaml_file=cf) for cf in config_files
-        ]
+        configs = [YamlConfigSettingsSource(settings_cls, yaml_file=cf) for cf in config_files]
         return (
             env_settings,
             init_settings,
@@ -349,7 +346,7 @@ class Config(BaseSettings):
     plot: Plot = Plot()
     plugins: dict[str, dict[str, Any]] = {}
     survey: Survey = Survey()
-    version: str = version("gisaf-backend")
+    version: str = __version__
     weather_station: dict[str, dict[str, Any]] = {}
     widgets: Widgets = Widgets()
 
diff --git a/src/gisaf/models/bootstrap.py b/src/gisaf/models/bootstrap.py
index 9455af2..fbd8d08 100644
--- a/src/gisaf/models/bootstrap.py
+++ b/src/gisaf/models/bootstrap.py
@@ -3,6 +3,7 @@ from pydantic import BaseModel
 from gisaf.config import conf, Map, Measures, Geo
 from gisaf.models.authentication import UserRead
 
+
 class Proj(BaseModel):
     srid: str
     srid_for_proj: str
@@ -16,4 +17,5 @@ class BootstrapData(BaseModel):
     geo: Geo = conf.geo
     measures: Measures = conf.measures
     redirect: str = conf.gisaf.redirect
-    user: UserRead | None = None # type: ignore
\ No newline at end of file
+    user: UserRead | None = None  # type: ignore
+
diff --git a/uv.lock b/uv.lock
index 436ba4c..30cf5b7 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1,4 +1,5 @@
 version = 1
+revision = 1
 requires-python = ">=3.12"
 
 [[package]]
@@ -276,7 +277,7 @@ name = "click"
 version = "8.1.7"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
-    { name = "colorama", marker = "platform_system == 'Windows'" },
+    { name = "colorama", marker = "sys_platform == 'win32'" },
 ]
 sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
 wheels = [
@@ -425,6 +426,18 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 },
 ]
 
+[[package]]
+name = "dunamai"
+version = "1.23.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/4e/a5c8c337a1d9ac0384298ade02d322741fb5998041a5ea74d1cd2a4a1d47/dunamai-1.23.0.tar.gz", hash = "sha256:a163746de7ea5acb6dacdab3a6ad621ebc612ed1e528aaa8beedb8887fccd2c4", size = 44681 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/21/4c/963169386309fec4f96fd61210ac0a0666887d0fb0a50205395674d20b71/dunamai-1.23.0-py3-none-any.whl", hash = "sha256:a0906d876e92441793c6a423e16a4802752e723e9c9a5aabdc5535df02dbe041", size = 26342 },
+]
+
 [[package]]
 name = "ecdsa"
 version = "0.19.0"
@@ -538,7 +551,6 @@ wheels = [
 
 [[package]]
 name = "gisaf-backend"
-version = "0.0.0"
 source = { editable = "." }
 dependencies = [
     { name = "aiopath" },
@@ -587,6 +599,7 @@ mqtt = [
 [package.dev-dependencies]
 dev = [
     { name = "asyncpg-stubs" },
+    { name = "dunamai" },
     { name = "ipdb" },
     { name = "pandas-stubs" },
     { name = "pretty-errors" },
@@ -599,17 +612,17 @@ dev = [
 
 [package.metadata]
 requires-dist = [
+    { name = "aiomqtt", marker = "extra == 'all'", specifier = ">=1.2.1" },
     { name = "aiomqtt", marker = "extra == 'mqtt'", specifier = ">=1.2.1" },
     { name = "aiopath", specifier = ">=0.7.1" },
     { name = "aiosqlite", specifier = ">=0.19.0" },
     { name = "apscheduler", specifier = ">=3.10.4" },
     { name = "asyncpg", specifier = ">=0.28.0" },
+    { name = "contextily", marker = "extra == 'all'", specifier = ">=1.4.0" },
     { name = "contextily", marker = "extra == 'contextily'", specifier = ">=1.4.0" },
     { name = "fastapi", specifier = ">=0.111" },
     { name = "geoalchemy2", specifier = ">=0.14.2" },
     { name = "geopandas", specifier = ">=1.0.1" },
-    { name = "gisaf-backend", extras = ["contextily"], marker = "extra == 'all'" },
-    { name = "gisaf-backend", extras = ["mqtt"], marker = "extra == 'all'" },
     { name = "httpx", specifier = ">=0.28.1" },
     { name = "itsdangerous", specifier = ">=2.1.2" },
     { name = "matplotlib", specifier = ">=3.8.3" },
@@ -633,10 +646,12 @@ requires-dist = [
     { name = "uvicorn", specifier = ">=0.23.2" },
     { name = "websockets", specifier = ">=12.0" },
 ]
+provides-extras = ["all", "contextily", "mqtt"]
 
 [package.metadata.requires-dev]
 dev = [
     { name = "asyncpg-stubs", specifier = ">=0.29.1" },
+    { name = "dunamai", specifier = ">=1.23.0" },
     { name = "ipdb", specifier = ">=0.13.13" },
     { name = "pandas-stubs", specifier = ">=2.1.4.231218" },
     { name = "pretty-errors", specifier = ">=1.2.25" },
@@ -1207,6 +1222,7 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712 },
     { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155 },
     { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356 },
+    { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 },
 ]
 
 [[package]]
@@ -1786,7 +1802,7 @@ name = "tzlocal"
 version = "5.2"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
-    { name = "tzdata", marker = "platform_system == 'Windows'" },
+    { name = "tzdata", marker = "sys_platform == 'win32'" },
 ]
 sdist = { url = "https://files.pythonhosted.org/packages/04/d3/c19d65ae67636fe63953b20c2e4a8ced4497ea232c43ff8d01db16de8dc0/tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e", size = 30201 }
 wheels = [