diff --git a/.gitignore b/.gitignore
index f49b592282bb3b64e26250494dcdb88fc39a8c6a..801bf4d0eeb309320403c6688cc405f2870462e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,4 @@ _work/
 .pytest_cache/
 results*/
 trainlog.pdf
+*_version.py
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0ad8ee2de5c592a4bb1e6fab2600b5e4a2841d17..771c784da441b37be1245d3012c98a2836d2621c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -12,6 +12,12 @@ variables:
   GIT_SUBMODULE_DEPTH: 1
   XDG_CONFIG_HOME: $CI_PROJECT_DIR/tests/data
 
+.snippets:
+  setup-metadata:
+    - pip install hatch
+    - PACKAGE_NAME=$(hatch project metadata name)
+    - PACKAGE_VERSION=$(hatch project metadata version)
+
 documentation:
   before_script:
     # for opencv-python dependence
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 5b589c033900e970b0d7092b59bb32904266314b..0000000000000000000000000000000000000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,7 +0,0 @@
-# SPDX-FileCopyrightText: Copyright © 2023 Idiap Research Institute <contact@idiap.ch>
-#
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-recursive-include doc *.rst *.png
-recursive-include tests *.py *.png *.csv *.json
-recursive-include src/mednet/config/data *.json *.json.bz2
diff --git a/conda/meta.yaml b/conda/meta.yaml
index 33434fb6550f0a393de6e6487f79e637238be3cc..66ef954d2294e085f5ceb90e9744c23190b6b9f0 100644
--- a/conda/meta.yaml
+++ b/conda/meta.yaml
@@ -6,14 +6,14 @@
 
 package:
   name: {{ data['project']['name'] }}
-  version: {{ data['project']['version'] }}
+  version: {{ environ.get('PACKAGE_VERSION') }}
 
 source:
   path: ..
 
 build:
   noarch: python
-  number: {{ environ.get('USE_BUILD_NUMBER', 0) }}
+  number: {{ environ.get('NEXT_BUILD_NUMBER', 0) }}
   run_exports:
     - {{ pin_subpackage(data['project']['name']) }}
   script:
@@ -23,6 +23,8 @@ requirements:
   host:
     - python >=3.10
     - pip
+    - hatchling
+    - versioningit
     - clapper {{ clapper }}
     - click {{ click }}
     - credible {{ credible }}
@@ -43,6 +45,7 @@ requirements:
     - lightning >=2.2.0
   run:
     - python >=3.10
+    - versioningit
     - {{ pin_compatible('clapper') }}
     - {{ pin_compatible('click') }}
     - {{ pin_compatible('credible') }}
diff --git a/pyproject.toml b/pyproject.toml
index 11192a1dbb8b1bd7b8f1dbbe8a7034298eacfb52..f56c58a3aeb2574c1f049fc9a963141af9beedca 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,21 +3,22 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 [build-system]
-requires = ["setuptools>=61.0.0", "wheel"]
-build-backend = "setuptools.build_meta"
+requires = ["hatchling", "versioningit"]
+build-backend = "hatchling.build"
 
 [project]
 name = "mednet"
-version = "1.0.0b0"
+dynamic = ["version"]
 requires-python = ">=3.10"
 description = "Benchmarks for Computer-Aided Disease Detection from Medical Data."
-dynamic = ["readme"]
+readme = "README.md"
 license = { text = "GNU General Public License v3 (GPLv3)" }
 authors = [{ name = "Geoffrey Raposo", email = "geoffrey@raposo.ch" }]
 maintainers = [
   { name = "Andre Anjos", email = "andre.anjos@idiap.ch" },
   { name = "Daniel Carron", email = "daniel.carron@idiap.ch" },
 ]
+
 classifiers = [
   "Development Status :: 4 - Beta",
   "Intended Audience :: Developers",
@@ -26,6 +27,7 @@ classifiers = [
   "Programming Language :: Python :: 3",
   "Topic :: Software Development :: Libraries :: Python Modules",
 ]
+
 dependencies = [
   "clapper",
   "click",
@@ -44,8 +46,29 @@ dependencies = [
   "lightning>=2.2.0",
   "tensorboard",
   "grad-cam>=1.4.8",
+  "versioningit",
+]
+
+[tool.hatch.version]
+source = "versioningit"
+
+[tool.hatch.build.targets.sdist]
+include = [
+  "src/**/*.py",
+  "src/**/*.json",
+  "src/**/*.json.bz2",
+  "tests/**/*.py",
+  "tests/**/*.png",
+  "tests/**/*.csv",
+  "tests/**/*.json",
+  "doc/**/*.rst",
+  "doc/**/*.png",
+  "LICENSES/*.txt",
 ]
 
+[tool.hatch.build.targets.wheel]
+packages = ["src/mednet"]
+
 [project.urls]
 documentation = "https://www.idiap.ch/software/biosignal/software/docs/biosignal/software/mednet/main/sphinx/"
 homepage = "https://pypi.org/project/mednet"
@@ -235,16 +258,6 @@ nih-cxr14-padchest = "mednet.config.data.nih_cxr14_padchest.idiap"
 # montgomery-shenzhen-indian-padchest aggregated dataset
 montgomery-shenzhen-indian-padchest = "mednet.config.data.montgomery_shenzhen_indian_padchest.default"
 
-[tool.setuptools]
-zip-safe = true
-package-dir = { "" = "src" }
-
-[tool.setuptools.dynamic]
-readme = { file = "README.md" }
-
-[tool.distutils.bdist_wheel]
-universal = true
-
 [tool.isort]
 profile = "black"
 line_length = 80
@@ -261,27 +274,27 @@ junit_log_passing_tests = false
 
 [tool.numpydoc_validation]
 checks = [
-    "all",  # report on all checks, except the below
-    "ES01", # Not all functions require extended summaries
-    "EX01", # Not all functions require examples
-    "GL01", # Expects text to be on the line after the opening quotes but that is in direct opposition of the sphinx recommendations and conflicts with other pre-commit hooks.
-    "GL08", # Causes issues if we don't have a docstring at the top of the file. Disabling this might fail to catch actual missing docstrings.
-    "PR04", # numpydoc does not currently support PEP484 typehints, which we are using
-    "RT03", # Since sphinx is unable to understand type annotations we need to remove some types from 'Returns', which breaks this check.
-    "SA01", # We do not use Also sections
-    "SS06", # Summary will span multiple lines if too long because of reformatting by other hooks.
+  "all",  # report on all checks, except the below
+  "ES01", # Not all functions require extended summaries
+  "EX01", # Not all functions require examples
+  "GL01", # Expects text to be on the line after the opening quotes but that is in direct opposition of the sphinx recommendations and conflicts with other pre-commit hooks.
+  "GL08", # Causes issues if we don't have a docstring at the top of the file. Disabling this might fail to catch actual missing docstrings.
+  "PR04", # numpydoc does not currently support PEP484 typehints, which we are using
+  "RT03", # Since sphinx is unable to understand type annotations we need to remove some types from 'Returns', which breaks this check.
+  "SA01", # We do not use Also sections
+  "SS06", # Summary will span multiple lines if too long because of reformatting by other hooks.
 ]
 
-exclude = [  # don't report on objects that match any of these regex
-    '\.__len__$',
-    '\.__getitem__$',
-    '\.__iter__$',
-    '\.__exit__$',
+exclude = [ # don't report on objects that match any of these regex
+  '\.__len__$',
+  '\.__getitem__$',
+  '\.__iter__$',
+  '\.__exit__$',
 ]
 
-override_SS05 = [  # override SS05 to allow docstrings starting with these words
-    '^Process ',
-    '^Assess ',
-    '^Access ',
-    '^This',
+override_SS05 = [ # override SS05 to allow docstrings starting with these words
+  '^Process ',
+  '^Assess ',
+  '^Access ',
+  '^This',
 ]
diff --git a/setup.py b/setup.py
deleted file mode 100644
index f13f857e2d244c7e7b73f618bdddc789041a09b5..0000000000000000000000000000000000000000
--- a/setup.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# SPDX-FileCopyrightText: Copyright © 2023 Idiap Research Institute <contact@idiap.ch>
-#
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-from setuptools import setup
-
-setup()
diff --git a/src/mednet/scripts/utils.py b/src/mednet/scripts/utils.py
index 6524ed9d3f7a413790a0f2ae115fca5260131348..d05973d3313c6501fedcaf5b3716fe113d64df72 100644
--- a/src/mednet/scripts/utils.py
+++ b/src/mednet/scripts/utils.py
@@ -6,6 +6,7 @@
 import json
 import logging
 import pathlib
+import re
 import shutil
 
 import lightning.pytorch
@@ -120,17 +121,37 @@ def execution_metadata() -> dict[str, int | float | str]:
     # collects dependence information
     package_name = __package__.split(".")[0]
     requires = importlib.metadata.requires(package_name) or []
-    dependence_names = [k.split()[0] for k in requires]
+    dependence_names = [re.split(r"(\=|~|!|>|<|;|\s)+", k)[0] for k in requires]
     dependencies = {
         k: importlib.metadata.version(k)  # version number as str
         for k in dependence_names
         if importlib.util.find_spec(k) is not None  # if is installed
     }
 
+    # checks if the current version corresponds to a dirty (uncommitted) change
+    # set, issues a warning to the user
+    current_version = importlib.metadata.version(package_name)
+    try:
+        import versioningit
+
+        actual_version = versioningit.get_version(".", config={})
+        if current_version != actual_version:
+            logger.warning(
+                f"Version mismatch between current version set "
+                f"({current_version}) and actual version returned by "
+                f"versioningit ({actual_version}).  This typically happens "
+                f"when you commit changes locally and do not re-install the "
+                f"package. Run `pip install -e .` or equivalent to fix this."
+            )
+    except Exception as e:
+        # not in a git repo?
+        logger.debug(f"Error {e}")
+        pass
+
     data = {
         "datetime": datetime,
         "package-name": __package__.split(".")[0],
-        "package-version": importlib.metadata.version(package_name),
+        "package-version": current_version,
         "dependencies": dependencies,
         "user": __import__("getpass").getuser(),
         "conda-env": os.environ.get("CONDA_DEFAULT_ENV", ""),