diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 631ef5164e34ff6056c674e2feebc8b7c4514841..b8c1190a6178daae13e0fc0edf0e0a309d487f14 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -23,14 +23,15 @@
         "customizations": {
             "vscode": {
                 "extensions": [
-					"ms-azuretools.vscode-docker",
-					"golang.go",
-					"EditorConfig.EditorConfig",
-					"eamodio.gitlens",
-					"ms-vscode.makefile-tools",
-					"redhat.vscode-yaml",
-					"valentjn.vscode-ltex"
-				]
+                    "ms-azuretools.vscode-docker",
+                    "golang.go",
+                    "EditorConfig.EditorConfig",
+                    "eamodio.gitlens",
+                    "ms-vscode.makefile-tools",
+                    "redhat.vscode-yaml",
+                    "valentjn.vscode-ltex",
+                    "zxh404.vscode-proto3"
+                ]
             }
         },
         "mounts": [
diff --git a/.gitlab/ci/.build-binaries.yml b/.gitlab/ci/.build-binaries.yml
index 25d738ff406470847955eaf8330ed5d6260ec496..174203fdcd52622712b38ef0718612d6cf873a94 100644
--- a/.gitlab/ci/.build-binaries.yml
+++ b/.gitlab/ci/.build-binaries.yml
@@ -14,5 +14,7 @@ build-all-binaries:
           - artifacts/gosdnc
           - artifacts/orchestrator
           - artifacts/venv-manager
+          - artifacts/inventory-manager
+          - artifacts/plugin-registry
         expire_in: 1 week
     <<: *build-binaries
diff --git a/.gitlab/ci/.build-container-images.yml b/.gitlab/ci/.build-container-images.yml
index 7e23a663784c5cdfb64e0fe986cac0b7d52b8a51..da70dcba2d6ae122b967c2417c014750cbed26b0 100644
--- a/.gitlab/ci/.build-container-images.yml
+++ b/.gitlab/ci/.build-container-images.yml
@@ -66,6 +66,15 @@ build-plugin-registry-image:
         - docker push "$PLUGIN_REGISTRY_IMAGE_NAME:$CI_COMMIT_REF_SLUG"
     <<: *build
 
+build-inventory-manager-image:
+    script:
+        - INVENTORY_MANAGER_IMAGE_NAME="${CI_REGISTRY_IMAGE}/inventory-manager"
+        - docker buildx build -t "$INVENTORY_MANAGER_IMAGE_NAME:$CI_COMMIT_SHA" -f "${CI_PROJECT_DIR}/applications/inventory-manager/inventory-manager.Dockerfile" --build-arg "GOLANG_VERSION=$GOLANG_VERSION" --build-arg "GITLAB_PROXY=${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/" .
+        - docker push "$INVENTORY_MANAGER_IMAGE_NAME:$CI_COMMIT_SHA"
+        - docker tag "$INVENTORY_MANAGER_IMAGE_NAME:$CI_COMMIT_SHA" "$INVENTORY_MANAGER_IMAGE_NAME:$CI_COMMIT_REF_SLUG"
+        - docker push "$INVENTORY_MANAGER_IMAGE_NAME:$CI_COMMIT_REF_SLUG"
+    <<: *build
+
 build-integration-test-images:
     needs: ["build-controller-image"]
     script:
diff --git a/Makefile b/Makefile
index 6fb681e592d9fa0d8da9019475208b08afe41be3..083a9087d194d5b58dd5368f7ad58d0237620dec 100644
--- a/Makefile
+++ b/Makefile
@@ -58,9 +58,9 @@ lint: install-tools
 lint-fix: install-tools
 	./$(TOOLS_DIR)/golangci-lint run --config .golangci.yml --fix
 
-build: pre build-gosdn build-gosdnc build-plugin-registry build-venv-manager build-arista-routing-engine-app build-hostname-checker-app build-basic-interface-monitoring-app
+build: pre build-gosdn build-gosdnc build-plugin-registry build-venv-manager build-arista-routing-engine-app build-hostname-checker-app build-basic-interface-monitoring-app build-inventory-manager
 
-containerize-all: containerize-gosdn containerize-gosdnc containerize-plugin-registry
+containerize-all: containerize-gosdn containerize-gosdnc containerize-plugin-registry containerize-venv-manager containerize-arista-routing-engine-app containerize-inventory-manager
 
 generate-all-certs: pre generate-root-ca generate-gosdn-certs generate-gnmi-target-certs
 
diff --git a/README.md b/README.md
index 907eafaa51a3e28602d80f0d3fae68e375f39965..e8e273c2edceb61f62cffc6e994ee54d33e67b56 100644
--- a/README.md
+++ b/README.md
@@ -97,6 +97,40 @@ operations for managed network elements.
 
 In addition, we provide a simple Northbound-API (gRPC) for the controller [right here](https://code.fbi.h-da.de/danet/gosdn/-/tree/master/controller/api).
 
+To use the API, you can build a login method as is done in the `inventory-manager` in `utils.go`.
+You log in, create a session context with the returned token, then you can simply make API calls with this context.
+Example:
+
+In your `utils.go`
+```golang
+// Login logs in to the controller.
+func Login(conn *grpc.ClientConn, address, user, pw string) (string, error) {
+	loginResp, err := api.Login(context.Background(), address, user, pw)
+	if err != nil {
+		return "", err
+	}
+
+	return loginResp.GetToken(), nil
+}
+
+// createContextWithAuthorization creates a context with the token received after login.
+func createContextWithAuthorization(sessionToken string) context.Context {
+	md := metadata.Pairs("authorize", sessionToken)
+	return metadata.NewOutgoingContext(context.Background(), md)
+}
+```
+
+The code in your app:
+```golang
+import "code.fbi.h-da.de/danet/gosdn/controller/api"
+
+sessionToken, err := Login(conn, config.ControllerAddress, config.UserName, config.UserPW)
+ctx := createContextWithAuthorization(sessionToken)
+
+_, err := api.AddNetworkElement(i.sessionContext, i.controllerAddress, networkElement.Name, networkElement.UUID, &transportOptions, pluginUUID, pndUUID, []string{})
+```
+
+
 The gRPC services can also be reached using HTTP requests via the gRPC-Gateway. The fitting OpenAPI definitions can be found [here](https://code.fbi.h-da.de/danet/gosdn/-/tree/master/api/openapiv2?ref_type=heads). Note, that this is experimental and tested less well. If you want to use the controller in secure mode which implies it's mandatory to login and provide the received token in other requests via the HTTP header with the key-value pair:
 `"authorize: token"`.
 
@@ -335,6 +369,7 @@ The framework provides some basic code to easily set up the subscription to the
 
 Examples where the application framework is used can be found [here](https://code.fbi.h-da.de/danet/gosdn/-/tree/master/applications).
 
+
 ## Integration tests
 
 The integration tests are currently in its own folder named `integration-tests`, as they use a complete black box design.
diff --git a/applications/inventory-manager/README.md b/applications/inventory-manager/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..16a7bdc2ce65bdcdb28f1874258db4c311738ce3
--- /dev/null
+++ b/applications/inventory-manager/README.md
@@ -0,0 +1,35 @@
+# Inventory Management App
+
+## Overview
+The Inventory Management App is designed to manage connected devices of an SDN (Software-Defined Networking) controller. It provides functionalities to add, update, delete, and view devices.
+
+## Features
+- Add new devices
+- Update existing devices
+- Delete devices
+
+
+## Config file structure:
+
+```yaml
+ControllerAddress: "127.0.0.1:55055"
+PndID: "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d"
+UserName: "testuser"
+UserPW: "securepassword"
+AppName: "Inventory-Manager"
+NetworkElements:
+  - Name: "Device01"
+    UUID: "5e41c291-6121-4335-84f6-41e04b8bdaa2"
+    Address: "10.0.0.2:1337"
+    Username: "admin"
+    Password: "adminpassword"
+    PluginID: "823aad29-69be-42f0-b279-90f2c1b6a94d"
+    Tls: false
+  - Name: "Device02"
+    UUID: "5e41c291-6121-4335-84f6-41e04b8bdaa1"
+    Address: "hostname:7030"
+    Username: "user"
+    Password: "userpassword"
+    PluginID: "823aad29-69be-42f0-b279-90f2c1b6a94d"
+    Tls: true
+```
diff --git a/applications/inventory-manager/config/config.go b/applications/inventory-manager/config/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..4a5a1d5ca1a5cfc21e5bb89b9dcd03d260f70f6e
--- /dev/null
+++ b/applications/inventory-manager/config/config.go
@@ -0,0 +1,20 @@
+package config
+
+type Config struct {
+	ControllerAddress string           `yaml:"ControllerAddress"`
+	PndID             string           `yaml:"PndID"`
+	UserName          string           `yaml:"UserName"`
+	UserPW            string           `yaml:"UserPW"`
+	AppName           string           `yaml:"AppName"`
+	NetworkElements   []NetworkElement `yaml:"NetworkElements"`
+}
+
+type NetworkElement struct {
+	Name     string `yaml:"Name"`     // e.g. "Device01"
+	UUID     string `yaml:"UUID"`     // e.g. "0d24fbcf-67db-4aac-bf0f-8902b014a7ed"
+	Address  string `yaml:"Address"`  // e.g. "10.0.0.2:1337" or "hostname:7030"
+	Username string `yaml:"Username"` // e.g. "admin"
+	Password string `yaml:"Password"` // e.g. "adminpassword"
+	Tls      bool   `yaml:"Tls"`      // e.g. false
+	PluginID string `yaml:"PluginID"` // UUID e.g. "823aad29-69be-42f0-b279-90f2c1b6a94d"
+}
diff --git a/applications/inventory-manager/example.yml b/applications/inventory-manager/example.yml
new file mode 100644
index 0000000000000000000000000000000000000000..70829840307c7eb6b0c059910259ce6315ad41de
--- /dev/null
+++ b/applications/inventory-manager/example.yml
@@ -0,0 +1,34 @@
+ControllerAddress: "127.0.0.1:55055"
+PndID: "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d"
+UserName: "admin"
+UserPW: "TestPassword"
+AppName: "Inventory-Manager"
+NetworkElements:
+  - Name: "kms01"
+    UUID: "0ff33c82-7fe1-482b-a0ca-67565806ee4b"
+    Address: "kms01:7030"
+    Username: "admin"
+    Password: "admin"
+    Tls : false
+    PluginID: "823aad29-69be-42f0-b279-90f2c1b6a94d"
+  - Name: "kms02"
+    UUID: "5e41c291-6121-4335-84f6-41e04b8bdaa2"
+    Address: "kms02:7030"
+    Username: "admin"
+    Password: "admin"
+    Tls : false
+    PluginID: "823aad29-69be-42f0-b279-90f2c1b6a94d"
+  - Name: "kms03"
+    UUID: "f80db2c0-2480-46b9-b7d1-b63f954e8227"
+    Address: "kms03:7030"
+    Username: "admin"
+    Password: "admin"
+    Tls : false
+    PluginID: "823aad29-69be-42f0-b279-90f2c1b6a94d"
+  - Name: "kms04"
+    UUID: "968fd594-b0e7-41f0-ba4b-de259047a933"
+    Address: "kms04:7030"
+    Username: "admin"
+    Password: "admin"
+    Tls : false
+    PluginID: "823aad29-69be-42f0-b279-90f2c1b6a94d"
diff --git a/applications/inventory-manager/inventory-manager.Dockerfile b/applications/inventory-manager/inventory-manager.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..92bc8b6f99fc240bdf0424850083bd0688eb00d8
--- /dev/null
+++ b/applications/inventory-manager/inventory-manager.Dockerfile
@@ -0,0 +1,16 @@
+ARG GOLANG_VERSION=1.23
+ARG BUILDARGS
+ARG GITLAB_PROXY
+
+FROM ${GITLAB_PROXY}golang:$GOLANG_VERSION-bookworm AS builder
+
+WORKDIR /gosdn
+
+COPY . .
+
+RUN make build-inventory-manager
+
+FROM ${GITLAB_PROXY}debian:12
+COPY --from=builder /gosdn/artifacts/inventory-manager /inventory-manager
+
+ENTRYPOINT ["/inventory-manager"]
diff --git a/applications/inventory-manager/inventory-manager.Dockerfile.dockerignore b/applications/inventory-manager/inventory-manager.Dockerfile.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..af624c773ebfa5dc21e1c1b9e91afc5cafee4bd1
--- /dev/null
+++ b/applications/inventory-manager/inventory-manager.Dockerfile.dockerignore
@@ -0,0 +1,20 @@
+.git
+.gitlab
+build
+documentation
+mocks
+test
+clab-gosdn_csbi_arista_base
+clab-gosdn_sts_demo_basic
+.cobra.yaml
+.dockerignore
+.gitlab-ci.yaml
+ARCHITECTURE.md
+CONTRIBUTING.md
+README.md
+artifacts
+build-tools
+models/YangModels
+models/arista
+models/YangModels
+*.Dockerfile
diff --git a/applications/inventory-manager/inventoryManager/inventoryManager.go b/applications/inventory-manager/inventoryManager/inventoryManager.go
new file mode 100644
index 0000000000000000000000000000000000000000..f1943b87e7143ef7d74533c2543529d52d005be1
--- /dev/null
+++ b/applications/inventory-manager/inventoryManager/inventoryManager.go
@@ -0,0 +1,133 @@
+package inventorymanager
+
+import (
+	"context"
+	"time"
+
+	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
+	"code.fbi.h-da.de/danet/gosdn/applications/inventory-manager/config"
+	"code.fbi.h-da.de/danet/gosdn/controller/api"
+	"github.com/google/uuid"
+	"github.com/sirupsen/logrus"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
+)
+
+type InventoryManager struct {
+	pndID             string
+	controllerAddress string
+	grpcClientConn    *grpc.ClientConn
+	sessionContext    context.Context
+	sessionToken      string
+	//eventService          event.ServiceInterface
+	configNetworkElements []config.NetworkElement // NetworkElements from the config file
+}
+
+// InitializeInventoryManager sets up the inventory manager and logs in to the controller.
+func InitializeInventoryManager(config *config.Config) (*InventoryManager, error) {
+	// Create controller client object
+	newConn, err := grpc.NewClient(config.ControllerAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
+	if err != nil {
+		logrus.Errorf("Failed to create controller client with error: %s", err.Error())
+		return nil, err
+	}
+
+	var conn *grpc.ClientConn
+	var sessionToken string
+	logrus.Info("Connecting and logging in to controller ", config.ControllerAddress)
+	for {
+		sessionToken, err = Login(conn, config.ControllerAddress, config.UserName, config.UserPW)
+		if err == nil {
+			logrus.Info("Connected and logged in to Controller")
+			conn = newConn
+			break
+		}
+		logrus.Errorf("Failed to connect and log in to Controller with error: %s, retrying in 1 second", err.Error())
+		time.Sleep(1 * time.Second)
+	}
+
+	app, err := NewInventoryManager(conn, sessionToken, config.ControllerAddress, config.PndID, config.AppName, config.NetworkElements)
+	if err != nil {
+		return nil, err
+	}
+
+	return app, nil
+}
+
+func NewInventoryManager(grpcClientConn *grpc.ClientConn, sessionToken, controllerAddress, pndID, appName string, networkElements []config.NetworkElement) (*InventoryManager, error) {
+	ctx := createContextWithAuthorization(sessionToken)
+
+	// Not needed now, but later when lifecycle is implemented
+	//queueCredentials, err := getQueueCredentials(ctx, controllerAddress, appName, registrationToken)
+	//if err != nil {
+	//	return nil, err
+	//}
+	//
+	//eventService, err := event.NewEventService(
+	//	queueCredentials,
+	//	[]event.Topic{event.ManagedNetworkElement},
+	//)
+	//if err != nil {
+	//	return nil, err
+	//}
+
+	return &InventoryManager{
+		grpcClientConn: grpcClientConn,
+		sessionContext: ctx,
+		sessionToken:   sessionToken,
+		//eventService:          eventService,
+		pndID:                 pndID,
+		controllerAddress:     controllerAddress,
+		configNetworkElements: networkElements,
+	}, nil
+}
+
+// Run starts the InventoryManager. This will elad to it trying to create every network element from the config file.
+// It will try to create it indefinitely.
+// TODO: After creating a network element, it will ensure that the element exists in the controller.
+func (i *InventoryManager) Run() error {
+	i.createNetworkelements()
+	logrus.Info("Created all network elements.")
+	for {
+		// Management logic after creation should replace this sleep
+		time.Sleep(60 * time.Second)
+	}
+}
+
+func (i *InventoryManager) createNetworkelements() {
+IterateDevicesLoop:
+	for _, networkElement := range i.configNetworkElements {
+		transportOptions := tpb.TransportOption{
+			Address:  networkElement.Address,
+			Username: networkElement.Username,
+			Password: networkElement.Password,
+			Tls:      networkElement.Tls,
+			TransportOption: &tpb.TransportOption_GnmiTransportOption{
+				GnmiTransportOption: &tpb.GnmiTransportOption{},
+			},
+		}
+
+	AddDeviceLoop:
+		for {
+			pluginUUID, err := uuid.Parse(networkElement.PluginID)
+			if err != nil {
+				logrus.Errorf("No or wrong plugin UUID for device %s provided. Skipping device creation.", networkElement.Name)
+				continue IterateDevicesLoop
+			}
+			pndUUID, err := uuid.Parse(i.pndID)
+			if err != nil {
+				logrus.Errorf("No or wrong PND UUID for device %s provided. Skipping device creation.", networkElement.Name)
+				continue IterateDevicesLoop
+			}
+
+			_, err = api.AddNetworkElement(i.sessionContext, i.controllerAddress, networkElement.Name, networkElement.UUID, &transportOptions, pluginUUID, pndUUID, []string{})
+			if err != nil {
+				logrus.Errorf("Failed to create network element %s with error: %s. Sleeping one second and retrying...", networkElement.Name, err.Error())
+				time.Sleep(1 * time.Second)
+				continue AddDeviceLoop
+			}
+			break AddDeviceLoop
+		}
+		logrus.Infof("Successfully created network element %s", networkElement.Name)
+	}
+}
diff --git a/applications/inventory-manager/inventoryManager/util.go b/applications/inventory-manager/inventoryManager/util.go
new file mode 100644
index 0000000000000000000000000000000000000000..15764b4f492a61130a5ea3e35e7a3da9a08b74bc
--- /dev/null
+++ b/applications/inventory-manager/inventoryManager/util.go
@@ -0,0 +1,35 @@
+package inventorymanager
+
+import (
+	"context"
+
+	"code.fbi.h-da.de/danet/gosdn/controller/api"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/metadata"
+)
+
+//type CustomJwtClaims struct {
+//	jwt.StandardClaims
+//	Username string `json:"username,omitempty"`
+//}
+
+// Login logs in to the controller.
+func Login(conn *grpc.ClientConn, address, user, pw string) (string, error) {
+	loginResp, err := api.Login(context.Background(), address, user, pw)
+	if err != nil {
+		return "", err
+	}
+
+	return loginResp.GetToken(), nil
+}
+
+// createContextWithAuthorization creates a context with the token received after login.
+func createContextWithAuthorization(sessionToken string) context.Context {
+	md := metadata.Pairs("authorize", sessionToken)
+	return metadata.NewOutgoingContext(context.Background(), md)
+}
+
+// getQueueCredentials registers an app for the event system of the controller and returns the provided credentials.
+//func getQueueCredentials(ctx context.Context, controllerAddress, appName, registrationToken string) (string, error) {
+//	return registration.Register(ctx, controllerAddress, appName, registrationToken)
+//}
diff --git a/applications/inventory-manager/main.go b/applications/inventory-manager/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..a86f5f414c52f4a92ad548271e692cc0c805c330
--- /dev/null
+++ b/applications/inventory-manager/main.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+	"flag"
+	"log"
+	"os"
+
+	"code.fbi.h-da.de/danet/gosdn/applications/inventory-manager/config"
+	inventorymanager "code.fbi.h-da.de/danet/gosdn/applications/inventory-manager/inventoryManager"
+	"github.com/sirupsen/logrus"
+	"google.golang.org/grpc/resolver"
+	"gopkg.in/yaml.v3"
+)
+
+func main() {
+	configFile := flag.String("config", "", "app config file")
+	logLevel := flag.String("log", "info", "logrus lof level (debug, info, warn, error, fatal, panic)")
+	noGRPCPassthrough := flag.Bool("noGRPCPassthrough", false, "set the default resolve scheme for grpc to not use  passthrough, default is false")
+
+	flag.Parse()
+
+	if *noGRPCPassthrough {
+		logrus.Info("gRPC default resolver scheme is not set to passthrough. This might cause issues with the gRPC connection when no real DNS server is available as each gRPC requests requires a DNS request.")
+	} else {
+		logrus.Info("Setting gRPC default resolver scheme to passthrough. No DNS queries are being made when doing a gRPC request.")
+		resolver.SetDefaultScheme("passthrough")
+	}
+
+	// parse string, this is built-in feature of logrus
+	ll, err := logrus.ParseLevel(*logLevel)
+	if err != nil {
+		ll = logrus.InfoLevel
+		logrus.Warn("invalid log level, using default: ", ll)
+	}
+
+	// set global log level
+	logrus.Info("setting log level to ", ll)
+	logrus.SetLevel(ll)
+
+	logrus.Debugf("current config path: %s", *configFile)
+	file, err := os.ReadFile(*configFile)
+	if err != nil {
+		currentFolder, _ := os.Getwd()
+		log.Fatalf("error reading config file: %s, current folder: %s", *configFile, currentFolder)
+	}
+
+	appConfig := &config.Config{}
+	if err = yaml.Unmarshal(file, appConfig); err != nil {
+		logrus.Fatal(err)
+	}
+
+	// Just for printing the config
+	configBytes, err := yaml.Marshal(appConfig)
+	if err != nil {
+		logrus.Fatal("error marshaling config to YAML: ", err)
+	}
+	logrus.Debugf("config:\n%s", string(configBytes))
+
+	inventoryManager, err := inventorymanager.InitializeInventoryManager(appConfig)
+	if err != nil {
+		logrus.Fatal(err)
+	}
+
+	err = inventoryManager.Run()
+	if err != nil {
+		logrus.Fatal(err)
+	}
+}
diff --git a/makefiles/build/Makefile b/makefiles/build/Makefile
index 6ffa72f252ad2a8278218a81c842efef1ee99988..69a388bcd34694f25fc7c031071c55a05bbf80fd 100644
--- a/makefiles/build/Makefile
+++ b/makefiles/build/Makefile
@@ -35,3 +35,6 @@ build-basic-interface-monitoring-app: pre
 
 build-ws-events-app: pre
 	$(GOBUILD) -trimpath -o $(BUILD_ARTIFACTS_PATH)/ws-events ./applications/ws-events
+
+build-inventory-manager: pre
+	$(GOBUILD) -trimpath -o $(BUILD_ARTIFACTS_PATH)/inventory-manager ./applications/inventory-manager
diff --git a/makefiles/container/Makefile b/makefiles/container/Makefile
index 34b1e740ee458bc2d318d04d5132980e1252fda3..c9c21b9728af4606335ea425a187a55dd09f5ad1 100644
--- a/makefiles/container/Makefile
+++ b/makefiles/container/Makefile
@@ -22,3 +22,6 @@ containerize-hostname-checker-app:
 
 containerize-ws-events-app:
 	docker buildx build --rm -t ws-events-app -f applications/ws-events/ws-events.Dockerfile .
+
+containerize-inventory-manager:
+	docker buildx build --rm -t venv-manager --load -f applications/inventory-manager/inventory-manager.Dockerfile .