diff --git a/.gitignore b/.gitignore
index 4db0a5a30d39fc71365ae434455906339e839d3a..6fe82b4d7f6c460e5d300c48c4b908b873824acc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 *build*
+*install*
 *idea*
 *.so
 *.a
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fe735161de552fb33119df9519bddf894884209a..15fc608bbdf4f2ed47a2464f4006af4151bc4477 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,8 +3,10 @@ image: gitlab-registry.mpcdf.mpg.de/mpcdf/module-image
 check-recipes:
   before_script:
     - module purge
-    - module load cmake/3.22 doxygen git
+    - module load cmake/3.24 doxygen git
     - module load intel/21.5.0
+    - module load gsl
+    - module list
     - cmake --version
 
   variables:
@@ -48,6 +50,18 @@ check-recipes:
     - cmake --build 08_build
     - ctest --test-dir 08_build || FAILED=true
 
+    - cmake -S 09_install/external -B 09_install/external-build -DBUILD_SHARED_LIBS=ON
+    - cmake --build 09_install/external-build
+    - cmake --install 09_install/external-build --prefix 09_install/external-install
+
+    - export add_ROOT=$(pwd)/09_install/external-install
+    - cmake -S 09_install/internal -B 09_install/internal-build -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON -DCMAKE_INSTALL_RPATH=$(pwd)/09_install/internal-install/lib64
+    - cmake --build 09_install/internal-build
+    - ./09_install/internal-build/app
+    - cmake --install 09_install/internal-build --prefix 09_install/internal-install
+    - ldd 09_install/internal-install/bin/app
+    - ./09_install/internal-install/bin/app
+
     - cmake --preset dev -S 10_presets -B 10_dev
     - cmake --build 10_dev
     - ./10_dev/app
diff --git a/09_install/README.md b/09_install/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..8fd68f1930e0ca63deb8a9f652d7e73ff536856b
--- /dev/null
+++ b/09_install/README.md
@@ -0,0 +1,37 @@
+# How to install a project with CMake?
+
+This recipe consists of two folders. `external` resembles some third-party library. `internal` is your project
+consisting of a library and a frontend executable.
+
+## How to define your targets to facilitate installation?
+Since CMake 3.23 supports [FILE_SET](https://cmake.org/cmake/help/latest/command/target_sources.html#command:target_sources).
+If you define your header files within the `HEADERS` file set. They will be automatically installed into the include
+directory. If you use a lower CMake version you need to take care to install the headers yourself.
+
+## Install the files
+All targets that need to be installed need an [install](https://cmake.org/cmake/help/latest/command/install.html#targets)
+statement. For installing files manually (for example, header files if you do not use file sets) you can use a different 
+version of the [install](https://cmake.org/cmake/help/latest/command/install.html#files) command. With these install commands
+you can already install your library and application. However, if someone wants to include your library they need
+to manually link or write a find script. CMake has the concept of config files to solve this problem.
+
+## How to create config files?
+CMake can automatically generate files that import your targets. For this you can use another version of the 
+[install](https://cmake.org/cmake/help/latest/command/install.html#export) command. If you have external dependencies,
+you have to make sure they are available when someone links against your library. This is done using
+[find_dependency](https://cmake.org/cmake/help/latest/module/CMakeFindDependencyMacro.html). You can see how all of this
+looks like in the external subfolder.
+
+## Installing into non-standard locations and linking against dependencies in non-standard locations.
+A design decision of CMake is to clear all `rpath`s when you install executables or shared libraries. The idea behind this
+is everything gets installed into standard locations and can be found at runtime. It also makes sure you do not link against
+libraries in your build folder. This is bad practice as one should be able to delete the build folder after installation.
+
+However, in some situations this behaviour is unwanted. For example, if you link against libraries installed in
+non-standard locations. In this case you will not find the libraries at runtime. CMake can keep all `rpath`s pointing
+into locations that are not within your build folder. This is achieved by 
+[CMAKE_INSTALL_RPATH_USE_LINK_PATH](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_RPATH_USE_LINK_PATH.html).
+
+Unfortunately your installation might still fail if you have a local library in your build folder and install into
+a non-standard location. Then this library will not be found at runtime. You can solve this by manually specifying your
+installation directory as a rpath. [CMAKE_INSTALL_RPATH](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_RPATH.html)
\ No newline at end of file
diff --git a/09_install/external/CMakeLists.txt b/09_install/external/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0631a40e8716aec3ce12ba6b6f82d4e308b363d8
--- /dev/null
+++ b/09_install/external/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.23)
+
+project(09_external
+        LANGUAGES CXX)
+
+# create a simple add library
+add_library(add)
+
+# It is good practice to put everything into namespaces to avoid name collisions.
+# A consistent view for internal as well as external projects can be achieved by
+# providing an alias target. This target should be equivalent with how a find script
+# imports the library.
+add_library(add::add ALIAS add)
+
+# add the source files to the library
+target_sources(add
+    PRIVATE add/add.cpp
+    PUBLIC FILE_SET HEADERS
+    BASE_DIRS ${PROJECT_SOURCE_DIR}
+    FILES add/add.hpp)
+
+# install the library, i.e. copying all the files
+install(
+    TARGETS add
+    EXPORT addTargets
+    FILE_SET HEADERS)
+
+# generate a config file that can be used by find_package
+install(
+    EXPORT addTargets
+    FILE addConfig.cmake
+    NAMESPACE add::
+    DESTINATION lib/cmake/add)
+
diff --git a/09_install/external/add/add.cpp b/09_install/external/add/add.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2998a6cfa5287dd5be6804d9f6f959704c4a8dd8
--- /dev/null
+++ b/09_install/external/add/add.cpp
@@ -0,0 +1,3 @@
+#include "add.hpp"
+
+double add(const double& lhs, const double& rhs) { return lhs + rhs; }
diff --git a/09_install/external/add/add.hpp b/09_install/external/add/add.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..333800d08697bcbb79c3be4cd79d3db26673b745
--- /dev/null
+++ b/09_install/external/add/add.hpp
@@ -0,0 +1,3 @@
+#pragma once
+
+double add(const double& lhs, const double& rhs);
diff --git a/09_install/internal/CMakeLists.txt b/09_install/internal/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0eef3a8854d591ba698548e675b7c15b07b3dfc0
--- /dev/null
+++ b/09_install/internal/CMakeLists.txt
@@ -0,0 +1,58 @@
+cmake_minimum_required(VERSION 3.23)
+
+project(09_library
+        LANGUAGES CXX)
+
+# make cache variables for install destinations
+include(GNUInstallDirs)
+
+# create a simple math library
+add_library(math)
+
+# It is good practice to put everything into namespaces to avoid name collisions.
+# A consistent view for internal as well as external projects can be achieved by
+# providing an alias target. This target should be equivalent with how a find script
+# imports the library.
+add_library(math::math ALIAS math)
+
+# add the source files to the library
+target_sources(math
+    PRIVATE math/math.cpp
+    PUBLIC FILE_SET HEADERS
+    BASE_DIRS ${PROJECT_SOURCE_DIR}
+    FILES math/math.hpp)
+
+find_package(GSL REQUIRED)
+target_link_libraries(math PRIVATE GSL::gsl)
+
+# install the library, i.e. copying all the files
+install(
+    TARGETS math
+    EXPORT mathTargets
+    FILE_SET HEADERS)
+
+# generate a config file that can be used by find_package
+install(
+    EXPORT mathTargets
+    FILE mathTargets.cmake
+    NAMESPACE math::
+    DESTINATION lib/cmake/math)
+
+configure_file(math/mathConfig.cmake.in mathConfig.cmake @ONLY)
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/mathConfig.cmake"
+        DESTINATION lib/cmake/math
+        )
+
+# =================================================================================
+
+# add an internal executable using this library.
+add_executable(app app/main.cpp)
+target_link_libraries(app PRIVATE math::math)
+
+find_package(add REQUIRED)
+target_link_libraries(app PRIVATE add::add)
+
+# No need for exports since this executable is not intended to be included in
+# another project.
+install(TARGETS app)
+
diff --git a/09_install/internal/app/main.cpp b/09_install/internal/app/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1f84ca1425fed9f4024018d1a81e698911732a3d
--- /dev/null
+++ b/09_install/internal/app/main.cpp
@@ -0,0 +1,9 @@
+#include <math/math.hpp>
+#include <add/add.hpp>
+#include <iostream>
+
+int main()
+{
+    std::cout << bessel(5.0) << std::endl;
+    std::cout << add(3, 4) << std::endl;
+}
diff --git a/09_install/internal/math/math.cpp b/09_install/internal/math/math.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7fc03f3043b5ffb4332a0fbc10a8a8d66e1af462
--- /dev/null
+++ b/09_install/internal/math/math.cpp
@@ -0,0 +1,3 @@
+#include <gsl/gsl_sf_bessel.h>
+
+double bessel(const double& x) { return gsl_sf_bessel_J0(x); }
diff --git a/09_install/internal/math/math.hpp b/09_install/internal/math/math.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0b335a1bd9ea0ad7a04518e5be633ed692069ad3
--- /dev/null
+++ b/09_install/internal/math/math.hpp
@@ -0,0 +1,3 @@
+#pragma once
+
+double bessel(const double& x);
diff --git a/09_install/internal/math/mathConfig.cmake.in b/09_install/internal/math/mathConfig.cmake.in
new file mode 100644
index 0000000000000000000000000000000000000000..d6280264825d8936fd1c574c61d0a9998f3572de
--- /dev/null
+++ b/09_install/internal/math/mathConfig.cmake.in
@@ -0,0 +1,7 @@
+include(CMakeFindDependencyMacro)
+
+# Same syntax as find_package
+find_dependency(GSL)
+
+# Add the targets file
+include("${CMAKE_CURRENT_LIST_DIR}/mathTargets.cmake")
diff --git a/README.md b/README.md
index 58c37df63eec6aff0c07c3f6d488196ed7d2d7a7..fdcaede61f2690becf483c463dd7d4ffbcffcb3d 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ we run them in our CI.
  * [How to get the current git hash at configure time?](06_git_configure)
  * [How to get the current git hash at build time?](07_git_build)
  * [How to manage all the tests in your project?](08_ctest)
+ * [How to install a project with CMake?](09_install)
  * [How to add compilation configurations to your project?](10_presets)
 
 ## Why CMake?