diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 73279037fd5d4389ce02824c4f882caba4456b27..6067a5110649ce2713cd7eeea32ebc426bc68f82 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v2.3.0
+    rev: v3.4.0
     hooks:
     -   id: trailing-whitespace
         args: [--markdown-linebreak-ext=md, --markdown-linebreak-ext=pandoc]
@@ -10,6 +10,7 @@ repos:
     -   id: check-merge-conflict
     -   id: check-xml
     -   id: check-yaml
+        exclude: 'scripts/ci/*'
     -   id: check-toml
 -   repo: local
     hooks:
diff --git a/CMakePresets.json b/CMakePresets.json
index a396bed3afac3409573ada2e1432c72810a55043..0e28a4c22f54036718268311766e1245681efa18 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -9,7 +9,7 @@
     {
       "name": "_binary_dir",
       "hidden": true,
-      "binaryDir": "${sourceDir}/build/${presetName}"
+      "binaryDir": "${sourceParentDir}/build/${presetName}"
     },
     {
       "name": "_release",
diff --git a/scripts/ci/extends/template-build-linux.yml b/scripts/ci/extends/template-build-linux.yml
index 06840e8708c8a9f8b33285698e7324225a899992..19e102a27c726a2d9a3c84374ebf4cff49740294 100644
--- a/scripts/ci/extends/template-build-linux.yml
+++ b/scripts/ci/extends/template-build-linux.yml
@@ -6,11 +6,14 @@
 
   before_script:
     - "echo \"For debugging run: docker run --rm -it -v $CI_BUILDS_DIR:/builds -w=$CI_PROJECT_DIR $CI_JOB_IMAGE\""
+    - build_dir=${BUILD_DIR:-../build/$CMAKE_PRESET}
+    - rm -rf $build_dir
+    - mkdir -p $build_dir
+    - ln -s ../build .
     - ([ "${CONAN_USER_HOME}" == "${CI_PROJECT_DIR}/.conan" ]) && conan remove --system-reqs '*'
     - ([[ $BUILD_CTEST_LARGE_ON_MASTER && "${CI_COMMIT_BRANCH}" == "master" && "${CMAKE_ARGS}" == *"USE_PYTHON=ON"* ]]) && export BUILD_CTEST_LARGE=true
   script:
     - cmake -S . --preset=$CMAKE_PRESET $CMAKE_ARGS -DOGS_BUILD_PROCESSES=$BUILD_PROCESSES
-    - build_dir=${BUILD_DIR:-build/$CMAKE_PRESET}
     - cd $build_dir
     - |
       if [[ -z "$TARGETS" ]]; then
diff --git a/scripts/ci/extends/template-build-win.yml b/scripts/ci/extends/template-build-win.yml
index e2927c09d6a4271aa0b986775415ba2651bbc7fe..1c36b01468c005edd4a3c74f2ee2fcc9e164577e 100644
--- a/scripts/ci/extends/template-build-win.yml
+++ b/scripts/ci/extends/template-build-win.yml
@@ -8,7 +8,11 @@
   dependencies: [meta]
   variables:
   script:
-    - $build_directory = if ($env:BUILD_DIR) { $env:BUILD_DIR } else { "build/" + $env:CMAKE_PRESET }
+    - $build_directory = if ($env:BUILD_DIR) { $env:BUILD_DIR } else { "..\build\" + $env:CMAKE_PRESET }
+    - (rm -r -fo $build_directory)
+    - cmd /c if not exist $build_directory mkdir $build_directory
+    # Create symlink https://stackoverflow.com/a/34905638/80480
+    - cmd /c mklink /D build ..\build
     - $cmake_cmd = "cmake -S . --preset=$env:CMAKE_PRESET
       $env:CMAKE_ARGS
       -DOGS_BUILD_PROCESSES=$env:BUILD_PROCESSES"
diff --git a/scripts/ci/jobs/build-linux-frontend.yml b/scripts/ci/jobs/build-linux-frontend.yml
index b060a49f5ea723e8940de80bc0d891167c42f64d..ff068ee51937b4c353679121c268c4b102068f4d 100644
--- a/scripts/ci/jobs/build-linux-frontend.yml
+++ b/scripts/ci/jobs/build-linux-frontend.yml
@@ -12,4 +12,5 @@ build linux frontend:
     CMAKE_PRESET: release
     CMAKE_ARGS: "-DOGS_USE_CONAN=OFF -DOGS_USE_PYTHON=OFF -DOGS_CPU_ARCHITECTURE=generic"
   before_script:
+    - !reference [.template-build-linux, before_script]
     - source scripts/env/eve/cli.sh
diff --git a/scripts/ci/jobs/build-linux.yml b/scripts/ci/jobs/build-linux.yml
index e88d27c8ed4ae563fdb4c5747096e86495414b49..b9f81ba3b12e6fff1de45c837407f42a5bcd5984 100644
--- a/scripts/ci/jobs/build-linux.yml
+++ b/scripts/ci/jobs/build-linux.yml
@@ -47,7 +47,7 @@ build linux (no unity):
   needs: [meta, "pre commit"]
   timeout: 1h
   variables:
-    BUILD_DIR: "build/no-unity"
+    BUILD_DIR: "../build/no-unity"
     BUILD_TESTS: "false"
     BUILD_CTEST: "false"
     CMAKE_PRESET: release
diff --git a/scripts/ci/jobs/build-mac.yml b/scripts/ci/jobs/build-mac.yml
index 6ec7e87f642654450f3bd54a7ffa9f8c04343aba..6ed9a7a51a74cfbc1ccd4e95b9c91cc05c6e0572 100644
--- a/scripts/ci/jobs/build-mac.yml
+++ b/scripts/ci/jobs/build-mac.yml
@@ -6,7 +6,7 @@ build mac:
     - .test-artifacts
   needs: [meta]
   variables:
-    BUILD_DIR: "build/mac-release"
+    BUILD_DIR: "../build/mac-release"
     CMAKE_PRESET: release
     CMAKE_ARGS: >-
       -DOGS_INSTALL_DEPENDENCIES=ON
diff --git a/scripts/ci/jobs/build-win.yml b/scripts/ci/jobs/build-win.yml
index 5fc3a970ff0f9b8247f5760902ee31e98780fd47..baae2c5e22aa03d7d1f75aeb5c4005236e7f1f07 100644
--- a/scripts/ci/jobs/build-win.yml
+++ b/scripts/ci/jobs/build-win.yml
@@ -7,7 +7,7 @@ build win:
     - when: manual
       allow_failure: true
   variables:
-    BUILD_DIR: build/win-release
+    BUILD_DIR: ..\build\win-release
     CMAKE_PRESET: release
     CMAKE_ARGS: >-
       -DOGS_CI_TESTRUNNER_REPEAT=1
diff --git a/scripts/ci/jobs/check-header.yml b/scripts/ci/jobs/check-header.yml
index e0ce70d3d63cd31c2a5acea9f8e469f407308bd2..1468fdf85c66925a30e519c9c2ad4a05dbbf1e9f 100644
--- a/scripts/ci/jobs/check-header.yml
+++ b/scripts/ci/jobs/check-header.yml
@@ -5,7 +5,7 @@ check header:
     - .rules-master-manual
   dependencies: [meta]
   variables:
-    BUILD_DIR: "build-check-header"
+    BUILD_DIR: "../build/check-header"
     CMAKE_ARGS: "-DOGS_CHECK_HEADER_COMPILATION=ON -DOGS_BUILD_GUI=ON -DBUILD_SHARED_LIBS=ON"
   image: $CONTAINER_GCC_GUI_IMAGE
   script:
diff --git a/scripts/ci/jobs/checks.yml b/scripts/ci/jobs/checks.yml
index e02ffef696c6595b222dae11e283f7847b1b9c0b..c3a993ce5ef828290f040e32451594f799aaa631 100644
--- a/scripts/ci/jobs/checks.yml
+++ b/scripts/ci/jobs/checks.yml
@@ -10,10 +10,7 @@ compiler warnings:
     - job: build mac
   script:
     - exit_code=0
-    - cd build/no-unity
-    - "if [[ $(cat make.output | grep warning -i) ]]; then printf 'There were GCC compiler warnings:\n\n'; cat make.output | grep warning -i; exit_code=1; fi"
-    - cd ../win-release
-    - "if [[ $(cat make.output | grep ': warning' -i) ]]; then printf 'There were MSVC compiler warnings:\n\n'; cat make.output | grep ': warning' -i; exit_code=1; fi"
-    - cd ../mac-release
-    - "if [[ $(cat make.output | grep warning -i) ]]; then printf 'There were Clang (macOS) compiler warnings:\n\n'; cat make.output | grep warning -i; exit_code=1; fi"
+    - "if [[ $(cat build/no-unity/make.output | grep warning -i) ]]; then printf 'There were GCC compiler warnings:\n\n'; cat make.output | grep warning -i; exit_code=1; fi"
+    - "if [[ $(cat build/win-release/make.output | grep ': warning' -i) ]]; then printf 'There were MSVC compiler warnings:\n\n'; cat make.output | grep ': warning' -i; exit_code=1; fi"
+    - "if [[ $(cat build/mac-release/make.output | grep warning -i) ]]; then printf 'There were Clang (macOS) compiler warnings:\n\n'; cat make.output | grep warning -i; exit_code=1; fi"
     - exit $exit_code
diff --git a/scripts/ci/jobs/clang-sanitizer.yml b/scripts/ci/jobs/clang-sanitizer.yml
index 281d20e93b48f400e89a26fa2db8ea7c4bdc1795..20077bab3be872925d223e08be06da8b086819df 100644
--- a/scripts/ci/jobs/clang-sanitizer.yml
+++ b/scripts/ci/jobs/clang-sanitizer.yml
@@ -4,7 +4,7 @@ clang sanitizer:
     - .rules-manual
   needs: ["pre commit"]
   variables:
-    BUILD_DIR: "build-sanitizer"
+    BUILD_DIR: "../build/sanitizer"
     CMAKE_ARGS: "-DOGS_ADDRESS_SANITIZER=ON -DOGS_UNDEFINED_BEHAVIOR_SANITIZER=ON"
     UBSAN_OPTIONS: "print_stacktrace=1"
     LSAN_OPTIONS: "suppressions=$CI_PROJECT_DIR/scripts/test/leak_sanitizer.suppressions"
diff --git a/scripts/ci/jobs/clang-tidy.yml b/scripts/ci/jobs/clang-tidy.yml
index adf3829a0eba437fb43cfa58fe851a61bbc884ba..110eaf7921e30f493e1f365f5dc31a853f276d0a 100644
--- a/scripts/ci/jobs/clang-tidy.yml
+++ b/scripts/ci/jobs/clang-tidy.yml
@@ -4,7 +4,7 @@ clang tidy:
     - .rules-manual
   needs: ["pre commit"]
   variables:
-    BUILD_DIR: "build-tidy"
+    BUILD_DIR: "../build/tidy"
     CMAKE_ARGS: "-DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF -DCMAKE_CXX_CLANG_TIDY=clang-tidy-9"
 
   image:
diff --git a/scripts/ci/jobs/code-coverage.yml b/scripts/ci/jobs/code-coverage.yml
index b357898bc5906d23cf07606e49587a21864d5d02..fc95559ae5d3ec245be445e9ba989ea616a291c0 100644
--- a/scripts/ci/jobs/code-coverage.yml
+++ b/scripts/ci/jobs/code-coverage.yml
@@ -11,10 +11,11 @@ code coverage:
     CMAKE_PRESET: coverage
     TARGETS: "testrunner_coverage ctest_coverage"
   before_script:
-    # HACK to easier linking to the generated pages
+    - !reference [.template-build-linux, before_script]
+    # Shortcut to the generated pages:
     - echo '<meta http-equiv="REFRESH" content="0;URL=build/coverage/coverage_report/index.html">' >> Coverage.html
   after_script:
-    - cd build/coverage
+    - cd ../build/coverage
     - poetry run fastcov -C testrunner_coverage.info ctest_coverage.info --lcov -o coverage.info
     - poetry run fastcov -C testrunner_coverage.info ctest_coverage.info -o coverage.json
     - genhtml --demangle-cpp -o coverage_report coverage.info
diff --git a/web/content/docs/devguide/getting-started/build-configuration.md b/web/content/docs/devguide/getting-started/build-configuration.md
index 30e55e897c8e7236066a14e00389e595498e84fe..841b6fedbd6bd5f9d09f16b9cb458722c3cd9562 100644
--- a/web/content/docs/devguide/getting-started/build-configuration.md
+++ b/web/content/docs/devguide/getting-started/build-configuration.md
@@ -16,10 +16,9 @@ Before compiling the developer has to choose a configuration of the software. OG
 To separate source code from generated files such as compiled libraries, executables, test outputs and IDE projects we create build-directories. They can be placed arbitrarily. You can have as many build-directories as you like for e.g. different configurations but they will all use one source code directory. A typically directory structure:
 
 - `ogs-source-code` (or simply `ogs`)
-  - `build/release`
-  - `build/debug`
-  - `build/release-petsc`
-- `build` (can also be placed outside the source tree)
+- `build` (should be placed outside the source directory)
+  - `release`
+  - `debug`
 
 ## Configure with CMake
 
@@ -49,7 +48,7 @@ In the source directory run `cmake`:
 cmake -S . --preset=release
 ```
 
-This will create a build-directory in the source tree (`build/release`) with the default CMake options and the Release configuration.
+This will create a build-directory outside the source tree (`../build/release`) with the default CMake options and the Release configuration.
 
 Additionally you can pass any CMake variable or option with `-DVARIABLE_NAME=VALUE` (note the `-D` in front!) to the CMake command. You can also overwrite the generator with the `-G` parameter or the build-directory with the `-B` parameter (to see all available options just run `cmake --help`)
 
@@ -128,9 +127,9 @@ If you cannot use CMake presets (e.g. when your CMake installation does not supp
 
 ```bash
 # in ogs source-directory
-mkdir -p build/release
-cd build/release
-cmake ../.. -G Ninja -DCMAKE_BUILD_TYPE=Release
+mkdir -p ../build/release
+cd ../build/release
+cmake ../../ogs -G Ninja -DCMAKE_BUILD_TYPE=Release
 ```
 
 ## Option: Configure with a visual tool
diff --git a/web/content/docs/devguide/getting-started/build.md b/web/content/docs/devguide/getting-started/build.md
index b8866543b598b82919e593320effb1edc0db8c78..6afbee70d1c828d4de2bbc605e35b5017c0396aa 100644
--- a/web/content/docs/devguide/getting-started/build.md
+++ b/web/content/docs/devguide/getting-started/build.md
@@ -35,7 +35,7 @@ You can work normally in Visual Studio but remember that you have to make projec
 To build with the `ninja` tool on the shell go to your previously configured build directory and type `ninja`:
 
 ```bash
-cd build/release
+cd ../build/release
 ninja
 ```
 </div>