diff --git a/CMakeLists.txt b/CMakeLists.txt
index bdc4f924ba4dd5fda3b65643d70be80e52fcb30b..3695cb6d85a68baced219cc0d63dc3ad4d888c7f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -134,6 +134,7 @@ add_executable(catch-tests-boardcore
     src/tests/catch/test-sensormanager-catch.cpp
     src/tests/catch/xbee/test-xbee-parser.cpp
     src/tests/catch/test-modulemanager.cpp
+    src/tests/catch/test-dependencymanager.cpp
     src/tests/catch/test-MEA.cpp
     src/tests/catch/test-airbrakesInterp.cpp
     src/tests/catch/test-pitot.cpp
diff --git a/cmake/boardcore-host.cmake b/cmake/boardcore-host.cmake
index a0a685e46cd1549783fdb6626be877a60b50389b..6f22f2b1fde182ffdb17fb4b6470bc51de995bb8 100644
--- a/cmake/boardcore-host.cmake
+++ b/cmake/boardcore-host.cmake
@@ -57,6 +57,7 @@ set(BOARDCORE_HOST_SRC
     ${SBS_BASE}/src/shared/utils/TestUtils/TestHelper.cpp
     ${SBS_BASE}/src/shared/utils/Registry/RegistryFrontend.cpp
     ${SBS_BASE}/src/shared/utils/Registry/RegistrySerializer.cpp
+    ${SBS_BASE}/src/shared/utils/DependencyManager/DependencyManager.cpp
 )
 
 # Create a library specific for host builds
diff --git a/cmake/boardcore.cmake b/cmake/boardcore.cmake
index a95d56bd1dfa7ab560f5d28dcd60cdad9a249053..309394fcfbd8a65727d0878fc11c102e9f6906cc 100644
--- a/cmake/boardcore.cmake
+++ b/cmake/boardcore.cmake
@@ -135,6 +135,7 @@ set(BOARDCORE_SRC
     ${BOARDCORE_PATH}/src/shared/utils/Registry/RegistryFrontend.cpp
     ${BOARDCORE_PATH}/src/shared/utils/Registry/RegistrySerializer.cpp
     ${BOARDCORE_PATH}/src/shared/utils/Registry/Backend/FileBackend.cpp
+    ${BOARDCORE_PATH}/src/shared/utils/DependencyManager/DependencyManager.cpp
 )
 
 # Creates the Skyward::Boardcore::${BOARD_NAME} library
diff --git a/src/shared/utils/DependencyManager/DependencyManager.cpp b/src/shared/utils/DependencyManager/DependencyManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fe4d3357452d98ded20b57d0de23be2c188db6a8
--- /dev/null
+++ b/src/shared/utils/DependencyManager/DependencyManager.cpp
@@ -0,0 +1,115 @@
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Authors: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "DependencyManager.h"
+
+#include <cxxabi.h>
+#include <fmt/format.h>
+
+using namespace Boardcore;
+
+// Simple utility function to demangle type infos
+std::string type_name_demangled(const std::type_info& info)
+{
+    char* demangled =
+        abi::__cxa_demangle(info.name(), nullptr, nullptr, nullptr);
+    std::string demangled2{demangled};
+    std::free(demangled);
+
+    return demangled2;
+}
+
+void DependencyManager::graphviz(std::ostream& os)
+{
+    os << "digraph {" << std::endl;
+
+    for (auto& module : modules)
+    {
+        for (auto& dep : module.second.deps)
+        {
+            os << fmt::format("  \"{}({})\" -> \"{}({})\"", module.second.name,
+                              module.second.impl, modules[dep].name,
+                              modules[dep].impl)
+               << std::endl;
+        }
+    }
+
+    os << "}" << std::endl;
+}
+
+bool DependencyManager::inject()
+{
+    load_success = true;
+
+    for (auto& module : modules)
+    {
+        LOG_INFO(logger, "Configuring [{}]...", module.second.name);
+        DependencyInjector injector{*this, module.second};
+        module.second.ptr->inject(injector);
+    }
+
+    if (load_success)
+    {
+        LOG_INFO(logger, "Configuring successful!");
+    }
+    else
+    {
+        LOG_ERR(logger, "Failed to inject modules!");
+    }
+
+    return load_success;
+}
+
+bool DependencyManager::insertImpl(Injectable* ptr,
+                                   const std::type_info& module_info,
+                                   const std::type_info& impl_info)
+{
+    // Early check to see if ptr is nullptr, fail if that's the case
+    if (ptr == nullptr)
+        return false;
+
+    auto idx         = std::type_index{module_info};
+    auto module_name = type_name_demangled(module_info);
+    auto impl_name   = type_name_demangled(impl_info);
+
+    return modules.insert({idx, ModuleInfo{ptr, module_name, impl_name, {}}})
+        .second;
+}
+
+Injectable* DependencyInjector::getImpl(const std::type_info& module_info)
+{
+    auto idx  = std::type_index{module_info};
+    auto iter = manager.modules.find(idx);
+    if (iter == manager.modules.end())
+    {
+        manager.load_success = false;
+
+        std::string module_name = type_name_demangled(module_info);
+        LOG_ERR(logger, "[{}] requires [{}], but the latter is not present",
+                info.name, module_name);
+
+        return nullptr;
+    }
+
+    info.deps.push_back(idx);
+    return iter->second.ptr;
+}
diff --git a/src/shared/utils/DependencyManager/DependencyManager.h b/src/shared/utils/DependencyManager/DependencyManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..4a1826b2574c4c26c57747f12b5768bc904a6349
--- /dev/null
+++ b/src/shared/utils/DependencyManager/DependencyManager.h
@@ -0,0 +1,276 @@
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Authors: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <diagnostic/PrintLogger.h>
+
+#include <ostream>
+#include <string>
+#include <typeindex>
+#include <typeinfo>
+#include <unordered_map>
+#include <vector>
+
+namespace Boardcore
+{
+
+class DependencyInjector;
+class DependencyManager;
+
+/**
+ * @brief Interface for an injectable dependency.
+ */
+class Injectable
+{
+public:
+    virtual ~Injectable() = default;
+
+    /**
+     * @brief Invoked by the DependencyManager to inject dependencies.
+     * Override this method to retrieve dependencies from the injector via
+     * `DependencyInjector::get()`.
+     *
+     * @param injector Proxy class used to obtain dependencies.
+     */
+    virtual void inject(DependencyInjector &injector) {}
+};
+
+/**
+ * @brief Main DependencyManager class.
+ *
+ * This utility is meant to be used as a dependency injector for Skyward OBSW.
+ *
+ * Dependencies and dependents should inherit from `Injectable`. Normally though
+ * you should extend from `InjectableWithDeps` in case of dependencies.
+ *
+ * Here's a quick example (for more examples look at
+ * src/tests/catch/test-dependencymanager.cpp):
+ * @code{.cpp}
+ *
+ * // A simple direct dependency
+ * class MyDependency1 : public Injectable {};
+ *
+ * // Abstracting direct dependencies with a common interface
+ * class MyDependency2Iface {};
+ * class MyDependency2 : public Injectable, public MyDependency2Iface {};
+ *
+ * // A simple dependant (which can become a dependency itself)
+ * class MyDependant : public InjectableWithDeps<MyDependency1,
+ * MyDependency2Iface> {};
+ *
+ * DependencyManager dependency_mgr;
+ *
+ * // Initialize the dependencies
+ * MyDependency1 *dep1 = ;
+ * MyDependency2Iface *dep2 = new MyDependency2();
+ *
+ * dependency_mgr.insert<MyDependency1>(new MyDependency1());
+ * dependency_mgr.insert<MyDependency2Iface>(new MyDependency2());
+ * dependency_mgr.insert<MyDependant>(new MyDependant());
+ *
+ * // Inject and resolve all dependencies!
+ * dependency_mgr.inject();
+ *
+ * // Optionally, print the resulting graph
+ * dependency_mgr.graphviz(std::cout);
+ * @endcode
+ */
+class DependencyManager
+{
+    friend class DependencyInjector;
+
+private:
+    struct ModuleInfo
+    {
+        Injectable *ptr;
+        // Name of the module interface
+        std::string name;
+        // Name of the actual concrete implementation of this module interface
+        std::string impl;
+        std::vector<std::type_index> deps;
+    };
+
+public:
+    DependencyManager() {}
+
+    /**
+     * @brief Insert a new dependency.
+     *
+     * @param dependency Injectable to insert in the DependencyManager.
+     * @returns True if successful, false otherwise.
+     */
+    template <typename T>
+    [[nodiscard]] bool insert(T *dependency)
+    {
+        return insertImpl(dynamic_cast<Injectable *>(dependency), typeid(T),
+                          typeid(*dependency));
+    }
+
+    /**
+     * @brief Generate a gaphviz compatible output showing dependencies.
+     * Needs to be called after inject.
+     *
+     * @param os Output stream to write to.
+     */
+    void graphviz(std::ostream &os);
+
+    /**
+     * @brief Inject all dependencies into all inserted .
+     *
+     * @returns True if successful, false otherwise.
+     */
+    [[nodiscard]] bool inject();
+
+private:
+    [[nodiscard]] bool insertImpl(Injectable *ptr,
+                                  const std::type_info &module_info,
+                                  const std::type_info &impl_info);
+
+    Boardcore::PrintLogger logger =
+        Boardcore::Logging::getLogger("DependencyManager");
+
+    bool load_success = true;
+    std::unordered_map<std::type_index, ModuleInfo> modules;
+};
+
+/**
+ * @brief Proxy class used to obtain dependencies.
+ */
+class DependencyInjector
+{
+    friend class DependencyManager;
+
+private:
+    DependencyInjector(DependencyManager &manager,
+                       DependencyManager::ModuleInfo &info)
+        : manager(manager), info(info)
+    {
+    }
+
+public:
+    /**
+     * @brief Retrieve a specific dependencies, recording it and tracking
+     * unsatisfied dependencies.
+     *
+     * @returns The requested dependency or nullptr if not found.
+     */
+    template <typename T>
+    T *get()
+    {
+        return dynamic_cast<T *>(getImpl(typeid(T)));
+    }
+
+private:
+    Injectable *getImpl(const std::type_info &module_info);
+
+    Boardcore::PrintLogger logger =
+        Boardcore::Logging::getLogger("DependencyManager");
+
+    DependencyManager &manager;
+    DependencyManager::ModuleInfo &info;
+};
+
+namespace DependencyManagerDetails
+{
+
+// Storage implementation
+template <typename... Types>
+struct Storage
+{
+    // No-op, base case
+    void inject(DependencyInjector &injector) {}
+
+    // No-op, dummy get (this should never be reached)
+    template <typename T>
+    T *get()
+    {
+        return nullptr;
+    }
+};
+
+template <typename Type, typename... Types>
+struct Storage<Type, Types...> : public Storage<Types...>
+{
+    using Super = Storage<Types...>;
+
+    // Recursive implementation
+    Type *item = nullptr;
+
+    void inject(DependencyInjector &injector)
+    {
+        item = injector.get<Type>();
+        // Call parent function
+        Super::inject(injector);
+    }
+
+    template <typename T>
+    typename std::enable_if_t<std::is_same<T, Type>::value, T *> get()
+    {
+        return item;
+    }
+
+    template <typename T>
+    typename std::enable_if_t<!std::is_same<T, Type>::value, T *> get()
+    {
+        return Super::template get<T>();
+    }
+};
+
+// Find type in list implementation
+template <typename T, typename... Types>
+struct Contains : std::false_type
+{
+};
+
+template <typename T, typename Type, typename... Types>
+struct Contains<T, Type, Types...>
+    : std::integral_constant<bool, std::is_same<T, Type>::value ||
+                                       Contains<T, Types...>::value>
+{
+};
+
+}  // namespace DependencyManagerDetails
+
+template <typename... Types>
+class InjectableWithDeps : public Injectable
+{
+public:
+    virtual void inject(DependencyInjector &injector) override
+    {
+        storage.inject(injector);
+    }
+
+    template <typename T>
+    T *getModule()
+    {
+        static_assert(DependencyManagerDetails::Contains<T, Types...>::value,
+                      "Dependency T is not present in the dependencies");
+
+        return storage.template get<T>();
+    }
+
+private:
+    DependencyManagerDetails::Storage<Types...> storage;
+};
+
+}  // namespace Boardcore
diff --git a/src/tests/catch/test-dependencymanager.cpp b/src/tests/catch/test-dependencymanager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..863e87f5123f1529c438a5d2e783b12a60ba7866
--- /dev/null
+++ b/src/tests/catch/test-dependencymanager.cpp
@@ -0,0 +1,165 @@
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <utils/DependencyManager/DependencyManager.h>
+
+#include <catch2/catch.hpp>
+
+using namespace Boardcore;
+
+namespace Boardcore
+{
+class B;
+
+class A : public Injectable
+{
+public:
+    A() {}
+
+    void bing_a(bool value) { this->value = value; }
+
+    bool bong_a() { return value; }
+
+    void inject(DependencyInjector &getter) { b = getter.get<B>(); }
+
+private:
+    B *b       = nullptr;
+    bool value = false;
+};
+
+class B : public Injectable
+{
+public:
+    B() {}
+
+    void bing_b(bool value) { this->value = value; }
+
+    bool bong_b() { return value; }
+
+    void inject(DependencyInjector &getter) { a = getter.get<A>(); }
+
+private:
+    A *a       = nullptr;
+    bool value = false;
+};
+
+class CIface
+{
+public:
+    virtual void bing_c() = 0;
+    virtual bool bong_c() = 0;
+};
+
+class C : public CIface, public InjectableWithDeps<A, B>
+{
+public:
+    void bing_c() override
+    {
+        value = getModule<A>()->bong_a() && getModule<B>()->bong_b();
+    }
+
+    bool bong_c() override { return value; }
+
+private:
+    bool value = false;
+};
+
+class D : public Injectable
+{
+public:
+    void bing_d() { value = c->bong_c(); }
+
+    bool bong_d() { return value; }
+
+    void inject(DependencyInjector &getter) { c = getter.get<CIface>(); }
+
+private:
+    CIface *c  = nullptr;
+    bool value = false;
+};
+}  // namespace Boardcore
+
+TEST_CASE("DependencyManager - Circular dependencies")
+{
+    DependencyManager manager;
+
+    Boardcore::A *a = new Boardcore::A();
+    Boardcore::B *b = new Boardcore::B();
+
+    REQUIRE(manager.insert<Boardcore::A>(a));
+    REQUIRE(manager.insert<Boardcore::B>(b));
+    REQUIRE(manager.inject());
+
+    a->bing_a(true);
+    REQUIRE(a->bong_a());
+
+    a->bing_a(false);
+    REQUIRE(!a->bong_a());
+
+    b->bing_b(true);
+    REQUIRE(b->bong_b());
+
+    b->bing_b(false);
+    REQUIRE(!b->bong_b());
+}
+
+TEST_CASE("DependencyManager - Virtual Dependencies")
+{
+    DependencyManager manager;
+
+    Boardcore::A *a = new Boardcore::A();
+    Boardcore::B *b = new Boardcore::B();
+    Boardcore::C *c = new Boardcore::C();
+    Boardcore::D *d = new Boardcore::D();
+
+    REQUIRE(manager.insert<Boardcore::A>(a));
+    REQUIRE(manager.insert<Boardcore::B>(b));
+    REQUIRE(manager.insert<Boardcore::CIface>(c));
+    REQUIRE(manager.insert<Boardcore::D>(d));
+    REQUIRE(manager.inject());
+
+    a->bing_a(false);
+    b->bing_b(false);
+
+    c->bing_c();
+    REQUIRE(!c->bong_c());
+    d->bing_d();
+    REQUIRE(!d->bong_d());
+
+    a->bing_a(true);
+    b->bing_b(true);
+
+    c->bing_c();
+    REQUIRE(c->bong_c());
+    d->bing_d();
+    REQUIRE(d->bong_d());
+}
+
+TEST_CASE("DependencyManager - Inject fail")
+{
+    DependencyManager manager;
+
+    Boardcore::A *a = new Boardcore::A();
+
+    REQUIRE(manager.insert<Boardcore::A>(a));
+    REQUIRE_FALSE(manager.inject());
+}