diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a89284abf85cf76c9af147b477e958b61e5bc96e..b89453ec319c92bf5a7048f6b787c621771525d0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,44 +4,31 @@ stages:
   - "Documentation"
   - deploy
 
+.nox-base:
+  image: i10git.cs.fau.de:5005/pycodegen/pycodegen/nox:alpine
+  tags:
+    - docker
+
 linter:
+  extends: .nox-base
   stage: "Code Quality"
   needs: []
-  except:
-    variables:
-      - $ENABLE_NIGHTLY_BUILDS
-  image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
   script:
-    - flake8 src/pystencilssfg
-  tags:
-    - docker
+    - nox --session lint
 
 typechecker:
+  extends: .nox-base
   stage: "Code Quality"
   needs: []
-  except:
-    variables:
-      - $ENABLE_NIGHTLY_BUILDS
-  image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
   script:
-    - pip install mypy
-    - mypy src/pystencilssfg 
-  tags:
-    - docker
+    - nox --session typecheck
 
 testsuite:
+  extends: .nox-base
   stage: "Tests"
-  image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
   needs: []
-  tags:
-    - docker
-  before_script:
-    - pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils.git@v2.0-dev"
-    - pip install -e .[tests]
   script:
-    - pytest -v --cov=src/pystencilssfg --cov-report=term --cov-config=pyproject.toml
-    - coverage html
-    - coverage xml
+    - nox --session testsuite
   coverage: '/TOTAL.*\s+(\d+%)$/'
   artifacts:
     when: always
@@ -54,23 +41,17 @@ testsuite:
         path: coverage.xml
 
 build-documentation:
+  extends: .nox-base
   stage: "Documentation"
-  image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
   needs: []
-  before_script:
-    - pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils.git@v2.0-dev"
-    - pip install -e .[docs]
   script:
-    - cd docs
-    - make html
-  tags:
-    - docker
+    - nox --session docs
   artifacts:
     paths:
       - docs/build/html
 
 pages:
-  image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
+  image: alpine:latest
   stage: deploy
   script:
     - ls -l
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index dbcaaf155503325f1282cd74f7ba013ee5693221..e89a6e598cff4c6da65bbdf773237e891536995c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -13,6 +13,19 @@ As such, any submission of contributions via merge requests is considered as agr
 
 ## Developing `pystencils-sfg`
 
+### Prequesites
+
+To develop pystencils-sfg, you will need at least these packages:
+
+ - Python 3.10
+ - Git
+ - A C++ compiler supporting at least C++20 (gcc >= 10, or clang >= 10)
+ - GNU Make
+ - CMake
+ - Nox
+
+Before continuing, make sure that the above packages are installed on your machine.
+
 ### Fork and Clone
 
 To work within the `pystencils-sfg` source tree, first create a *fork* of this repository
@@ -29,28 +42,29 @@ source .venv/bin/activate
 pip install -e .
 ```
 
+If you have [nox](https://nox.thea.codes/en/stable/) installed, you can also set up your virtual environment
+by running `nox --session dev_env`.
+
 ### Code Style and Type Checking
 
 To contribute, please adhere to the Python code style set by [PEP 8](https://peps.python.org/pep-0008/).
-For consistency, format all your source files using the [black](https://pypi.org/project/black/) formatter.
-Use flake8 to check your code style:
+For consistency, format all your source files using the [black](https://pypi.org/project/black/) formatter,
+and check them regularily using the `flake8` linter through Nox:
 
 ```shell
-flake8 src/pystencilssfg
+nox --session lint
 ```
 
 Further, `pystencils-sfg` is being fully type-checked using [MyPy](https://www.mypy-lang.org/).
 All submitted code should contain type annotations ([PEP 484](https://peps.python.org/pep-0484/)) and must be
 correctly statically typed.
-Before each commit, check your types by calling
+Regularily check your code for type errors using
 
 ```shell
-mypy src/pystencilssfg
+nox --session typecheck
 ```
 
 Both `flake8` and `mypy` are also run in the integration pipeline.
-You can automate the code quality checks by running them via a git pre-commit hook.
-Such a hook can be installed using the [`install_git_hooks.sh`](install_git_hooks.sh) script located at the project root.
 
 ### Test Your Code
 
@@ -65,3 +79,11 @@ In [tests/generator_scripts](tests/generator_scripts), a framework is provided t
 for successful execution, correctness, and compilability of their output.
 Read the documentation within [test_generator_scripts.py](tests/generator_scripts/test_generator_scripts.py)
 for more information.
+
+Run the test suite by calling it through Nox:
+
+```shell
+nox --session testsuite
+```
+
+This will also collect coverage information and produce a coverage report as a HTML site placed in the `htmlcov` folder.
diff --git a/noxfile.py b/noxfile.py
new file mode 100644
index 0000000000000000000000000000000000000000..915ab84dcd70663a7c2c7644212e6aded5484d50
--- /dev/null
+++ b/noxfile.py
@@ -0,0 +1,88 @@
+from __future__ import annotations
+
+from typing import Sequence
+import nox
+
+nox.options.sessions = ["lint", "typecheck", "testsuite"]
+
+
+def add_pystencils_git(session: nox.Session):
+    """Clone the pystencils 2.0 development branch and install it in the current session"""
+    cache_dir = session.cache_dir
+
+    pystencils_dir = cache_dir / "pystencils"
+    if not pystencils_dir.exists():
+        session.run_install(
+            "git",
+            "clone",
+            "--branch",
+            "v2.0-dev",
+            "--single-branch",
+            "https://i10git.cs.fau.de/pycodegen/pystencils.git",
+            pystencils_dir,
+            external=True,
+        )
+    session.install("-e", str(pystencils_dir))
+
+
+def editable_install(session: nox.Session, opts: Sequence[str] = ()):
+    add_pystencils_git(session)
+    if opts:
+        opts_str = "[" + ",".join(opts) + "]"
+    else:
+        opts_str = ""
+    session.install("-e", f".{opts_str}")
+
+
+@nox.session(python="3.10", tags=["qa", "code-quality"])
+def lint(session: nox.Session):
+    """Lint code using flake8"""
+
+    session.install("flake8")
+    session.run("flake8", "src/pystencilssfg")
+
+
+@nox.session(python="3.10", tags=["qa", "code-quality"])
+def typecheck(session: nox.Session):
+    """Run MyPy for static type checking"""
+    editable_install(session)
+    session.install("mypy")
+    session.run("mypy", "src/pystencilssfg")
+
+
+@nox.session(python=["3.10"], tags=["tests"])
+def testsuite(session: nox.Session):
+    """Run the testsuite and measure coverage."""
+    editable_install(session, ["testsuite"])
+    session.run(
+        "pytest",
+        "-v",
+        "--cov=src/pystencilssfg",
+        "--cov-report=term",
+        "--cov-config=pyproject.toml",
+    )
+    session.run("coverage", "html")
+    session.run("coverage", "xml")
+
+
+@nox.session(python=["3.10"], tags=["docs"])
+def docs(session: nox.Session):
+    """Build the documentation pages"""
+    editable_install(session, ["docs"])
+    session.chdir("docs")
+    session.run("make", "html", external=True)
+
+
+@nox.session()
+def dev_env(session: nox.Session):
+    """Set up the development environment at .venv"""
+
+    session.install("virtualenv")
+    session.run("virtualenv", ".venv", "--prompt", "pystencils-sfg")
+    session.run(
+        ".venv/bin/pip",
+        "install",
+        "git+https://i10git.cs.fau.de/pycodegen/pystencils.git@v2.0-dev",
+        external=True,
+    )
+    session.run(".venv/bin/pip", "install", "-e", ".[dev]", external=True)
diff --git a/pyproject.toml b/pyproject.toml
index b787da5c7180e32c6751bc8c78c2532f693382e2..da36a11c4a19d510faadc491045485594790c535 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,10 +23,15 @@ requires = [
 build-backend = "setuptools.build_meta"
 
 [project.optional-dependencies]
-tests = [
-    "flake8>=6.1.0",
-    "mypy>=1.7.0",
+dev = [
+    "flake8",
+    "mypy",
     "black",
+    "clang-format",
+]
+testsuite = [
+    "pytest",
+    "pytest-cov",
     "pyyaml",
     "requests",
     "fasteners",
@@ -60,7 +65,7 @@ omit = [
 
 [tool.coverage.report]
 exclude_also = [
-    "\\.\\.\\.",
+    "\\.\\.\\.\n",
     "if TYPE_CHECKING:",
     "@(abc\\.)?abstractmethod",
 ]