diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index cec5c0e92de7ca7af945c3112ccf679c81e01e1e..42793e04f7d4ea41393a20611f8538cd13103900 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -153,10 +153,10 @@ deploy-pypi:
     - tags
 
   before_script:
-    - pip install twine
+    - pip install twine build
 
   script:
-    - python setup.py sdist bdist_wheel
+    - python -m build
     - twine upload -r pypi dist/*
 
 bump-python-deps:
@@ -167,7 +167,7 @@ bump-python-deps:
     - schedules
 
   script:
-    - devops python-deps requirements.txt doc-requirements.txt
+    - devops python-deps pyproject.toml doc-requirements.txt
 
 release-notes:
   stage: deploy
diff --git a/Dockerfile b/Dockerfile
index 6f5c337f1d4dd14a46b7148c0ed4e287c18e6341..eccae7a9b49799bd41180311b112e158c8096ad4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,8 +9,7 @@ WORKDIR /src
 
 # Copy DAN data
 COPY dan dan
-COPY requirements.txt *-requirements.txt setup.py VERSION README.md ./
+COPY pyproject.toml README.md ./
 
-# Install DAN as a package with GitLab package registry
-RUN --mount=type=secret,id=netrc,target=/root/.netrc \
-    pip install . --no-cache-dir
+# Install DAN as a package
+RUN pip install . --no-cache-dir
diff --git a/MANIFEST.in b/MANIFEST.in
index d998b5e006907ae4142ae978bed8eac45e1cc921..42e73fd359a4b894e0b98c5b2c82384910ab0179 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,7 +1,3 @@
 include LICENSE
 include LICENCE
-include requirements.txt
-include doc-requirements.txt
-include mlflow-requirements.txt
-include VERSION
 include README.md
diff --git a/Makefile b/Makefile
index 7325290c19392e5a4dae02866b78ca68e1ce7ed0..3cd57d501465235e9af8a35f231573516b6761ae 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,10 @@
 .PHONY: release
 
 release:
-	$(eval version:=$(shell cat VERSION))
-	echo $(version)
-	git commit VERSION -m "Version $(version)"
+	# Grep the version from pyproject.toml, squeeze multiple spaces, delete double and single quotes, get 3rd val.
+	# This command tolerates multiple whitespace sequences around the version number.
+	$(eval version:=$(shell grep -m 1 version pyproject.toml | tr -s ' ' | tr -d '"' | tr -d "'" | cut -d' ' -f3))
+	echo Releasing version $(version)
+	git commit pyproject.toml -m "Version $(version)"
 	git tag $(version)
 	git push origin main $(version)
diff --git a/README.md b/README.md
index 655fc156024f67fbc9261dd297468b43465c6f29..b91da1154446be1b238f605a8a122fed0fc59f32 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 
 For more details about this package, make sure to see the documentation available at <https://atr.pages.teklia.com/dan/>.
 
-This is an open-source project, licensed using [the MIT license](https://opensource.org/license/mit/).
+This is an open-source project, licensed using [the CeCILL-C license](https://cecill.info/index.en.html).
 
 ## Inference
 
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 8280595e7373ade02b747141dcf379223226bbee..0000000000000000000000000000000000000000
--- a/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-0.2.0rc5
diff --git a/doc-requirements.txt b/doc-requirements.txt
index 947c14d0f04651437904914e0aa6182f4f826688..a49bdc8aabadc39157dda585d3a892731e7d10c2 100644
--- a/doc-requirements.txt
+++ b/doc-requirements.txt
@@ -1,9 +1,6 @@
 black==24.3.0
-doc8==1.1.1
 # Pick a specific version because griffe==0.32.0 introduces a bug
 griffe==0.31.0
-mkdocs==1.4.2
 mkdocs-material==9.1.9
-mkdocstrings==0.20.0
 mkdocstrings-python==0.9.0
 recommonmark==0.7.1
diff --git a/docs/get_started/development.md b/docs/get_started/development.md
index f5098dc628eac0c2780d63dad61a378542e8b605..102abb078312fbc8952713750c64a73dcbbf8df5 100644
--- a/docs/get_started/development.md
+++ b/docs/get_started/development.md
@@ -77,11 +77,11 @@ This documentation uses [Sphinx](http://www.sphinx-doc.org/) and was generated u
 
 ### Setup
 
-Add the `docs` extra when installing `teklia-dan`:
+Install the needed dependencies through:
 
 ```shell
 # In a clone of the Git repository
-pip install .[docs]
+pip install -r doc-requirements.txt
 ```
 
 Build the documentation using `mkdocs serve -v`. You can then write in [Markdown](https://www.markdownguide.org/) in the relevant `docs/*.md` files, and see live output on http://localhost:8000.
diff --git a/mlflow-requirements.txt b/mlflow-requirements.txt
deleted file mode 100644
index 3dcc340d6ef2b4736208dff59727df00d9ec0b92..0000000000000000000000000000000000000000
--- a/mlflow-requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-mlflow-skinny==2.2.2
-pandas==2.0.0
diff --git a/pyproject.toml b/pyproject.toml
index 02f967beeffba19884cdf97d387fe0a2394a7604..fc561af2b651352a5f1e8100f81f6b3de9e92785 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,78 @@
+[build-system]
+requires = ["setuptools >= 61.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "atr-dan"
+version = "0.2.0rc5"
+description = "Teklia DAN"
+readme = "README.md"
+requires-python = ">=3.10"
+authors = [
+    { name = "Teklia", email = "contact@teklia.com" },
+]
+dependencies = [
+    "albumentations==1.3.1",
+    "arkindex-export==0.1.9",
+    "boto3==1.26.124",
+    "flashlight-text==0.0.7",
+    "imageio==2.26.1",
+    "imagesize==1.4.1",
+    "lxml==5.2.0",
+    "matplotlib==3.8.3",
+    "mdutils==1.6.0",
+    "nltk==3.8.1",
+    "numpy==1.24.3",
+    "PyYAML==6.0.1",
+    "scipy==1.12.0",
+    "sentencepiece==0.1.99",
+    "teklia-line-image-extractor==0.2.9",
+    "teklia-nerval==0.3.3-rc1",
+    "tenacity==8.2.3",
+    "tensorboard==2.12.2",
+    "torch==2.2.0",
+    "torchaudio==2.2.0",
+    "torchvision==0.17.0",
+    "tqdm==4.65.0",
+]
+
+keywords = ["python", "HTR", "OCR", "NER", "machine learning", "pytorch"]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "License :: CeCILL-C Free Software License Agreement (CECILL-C)",
+    # Specify the Python versions you support here.
+    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    # Topics
+    "Topic :: Software Development :: Libraries :: Python Modules",
+    "Topic :: Text Processing :: Linguistic",
+    "Topic :: Scientific/Engineering :: Artificial Intelligence",
+    "Topic :: Scientific/Engineering :: Image Recognition",
+]
+
+[project.optional-dependencies]
+mlflow = [
+    "mlflow-skinny==2.2.2",
+    "pandas==2.0.0",
+]
+
+[project.scripts]
+teklia-dan = "dan.cli:main"
+
+[project.urls]
+Homepage = "https://atr.pages.teklia/dan/"
+Documentation = "https://atr.pages.teklia/dan/"
+Repository = "https://gitlab.teklia.com/atr/dan"
+"Bug Tracker" = "https://gitlab.teklia.com/atr/dan/issues"
+Maintainers = "https://teklia.com"
+
+[tool.setuptools]
+license-files = ["LICENSE", "LICENCE"]
+
+[tool.setuptools.packages]
+find = {}
+
 [tool.ruff]
 exclude = [".git", "__pycache__"]
 
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 4f81b88128cc35deb882c1fd9678c7c8d63cb3c8..0000000000000000000000000000000000000000
--- a/requirements.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-albumentations==1.3.1
-arkindex-export==0.1.9
-boto3==1.26.124
-flashlight-text==0.0.7
-imageio==2.26.1
-imagesize==1.4.1
-lxml==5.2.0
-matplotlib==3.8.3
-mdutils==1.6.0
-nltk==3.8.1
-numpy==1.24.3
-PyYAML==6.0.1
-scipy==1.12.0
-sentencepiece==0.1.99
-teklia-line-image-extractor==0.2.9
-teklia-nerval==0.3.3-rc1
-tenacity==8.2.3
-tensorboard==2.12.2
-torch==2.2.0
-torchaudio==2.2.0
-torchvision==0.17.0
-tqdm==4.65.0
diff --git a/setup.py b/setup.py
deleted file mode 100755
index 1acba06f698137d1435d3d5a0f80706e6c25016c..0000000000000000000000000000000000000000
--- a/setup.py
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/env python
-# Copyright Teklia (contact@teklia.com) & Denis Coquenet
-# This code is licensed under CeCILL-C
-
-# -*- coding: utf-8 -*-
-
-from pathlib import Path
-from typing import List
-
-from setuptools import find_packages, setup
-
-
-def parse_requirements_line(line) -> str:
-    # Special case for git requirements
-    if line.startswith("git+http"):
-        assert "@" in line, "Branch should be specified with suffix (ex: @master)"
-        assert (
-            "#egg=" in line
-        ), "Package name should be specified with suffix (ex: #egg=kraken)"
-        package_name: str = line.split("#egg=")[-1]
-        return f"{package_name} @ {line}"
-    # Special case for submodule requirements
-    elif line.startswith("-e"):
-        package_path: str = line.split(" ")[-1]
-        package = Path(package_path).resolve()
-        return f"{package.name} @ file://{package}"
-    else:
-        return line
-
-
-def parse_requirements(filename: str) -> List[str]:
-    path = Path(__file__).parent.resolve() / filename
-    assert path.exists(), f"Missing requirements: {path}"
-    return list(
-        map(parse_requirements_line, map(str.strip, path.read_text().splitlines()))
-    )
-
-
-setup(
-    name="atr-dan",
-    version=Path("VERSION").read_text(),
-    description="Teklia DAN",
-    long_description=Path("README.md").read_text(),
-    long_description_content_type="text/markdown",
-    author="Teklia",
-    author_email="contact@teklia.com",
-    url="https://gitlab.teklia.com/atr/dan",
-    python_requires=">=3.10",
-    install_requires=parse_requirements("requirements.txt"),
-    packages=find_packages(),
-    entry_points={
-        "console_scripts": [
-            "teklia-dan=dan.cli:main",
-        ]
-    },
-    extras_require={
-        "docs": parse_requirements("doc-requirements.txt"),
-        "mlflow": parse_requirements("mlflow-requirements.txt"),
-    },
-    license="CeCILL-C",
-    license_files=(
-        "LICENSE",
-        "LICENCE",
-    ),
-)
diff --git a/tox.ini b/tox.ini
index c2b943f0ef7107b72420eb5f88a324b8bfc4a473..eba8879966c5ba292953123a1e4eaaef809fa241 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,13 +10,17 @@ wheel_build_env = .pkg
 deps =
     pytest>=6
     pytest-responses
-    -rrequirements.txt
 commands =
     pytest {tty:--color=yes} {posargs}
 
 [testenv:doc]
 extras = docs
 
+# No need to install dan in this env
+skip_install = True
+
+deps = -rdoc-requirements.txt
+
 commands =
   mkdocs build --clean --verbose --strict