diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000000000000000000000000000000000..32142e4fe2c1c0f445c1808489b5579208a9491e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+# EditorConfig is awesome: https://editorconfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
diff --git a/.gitignore b/.gitignore
index 7b86b0c38885f4f3418212a1f6ad0efc5fab4bbd..7973f4e0e80615ccb708844ab5b87f225bfac1c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 gen/*
+bin/*
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 863723aaa30f5fdc6ee2ac9d161759276c1ac987..1c9fa66cb7fdd053e4655186315c05023eb45648 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,43 +1,77 @@
 stages:
-  - build
-  - lint
-  #- test
-  #- analyze
+    - prepare
+    - build
+    - lint
+    - test
 
 variables:
-  IMAGE_PATH: "${CI_REGISTRY_IMAGE}"
-  GOLANG_VERSION: "1.24"
-  GOLANG_MINOR_VERSION: "${GOLANG_VERSION}.0"
-  DOCKER_TLS_CERTDIR: "/certs"
-
-lint-buf:
-  stage: lint
-  image:
-    name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/bufbuild/buf
-    entrypoint: [""]
-  script:
-    - buf format
-  needs: []
+    IMAGE_PATH: "${CI_REGISTRY_IMAGE}"
+    GOLANG_VERSION: "1.24"
+    GOLANG_MINOR_VERSION: "${GOLANG_VERSION}.0"
+    DOCKER_TLS_CERTDIR: "/certs"
+
+generate-api:
+    stage: prepare
+    image:
+        name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/bufbuild/buf
+        entrypoint: [""]
+    script:
+        - buf generate
+    artifacts:
+        untracked: true
+        paths:
+            - gen/
+
+lint-api:
+    stage: lint
+    image:
+        name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/bufbuild/buf
+        entrypoint: [""]
+    script:
+        - buf format
+    needs:
+        - []
+
+lint-go:
+    stage: lint
+    image:
+        name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/golangci/golangci-lint:v2.0.2
+    script:
+        - golangci-lint run
+    dependencies:
+        - generate-api
+
+test-go:
+    stage: test
+    image:
+        name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/golang:${GOLANG_VERSION}
+    script:
+        - go test ./...
+    dependencies:
+        - generate-api
 
 # Build stage
 .build: &build
-  stage: build
-  image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/docker:latest
-  services:
-    - name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/docker:latest
-      alias: docker
-  before_script:
-    - apk add git
-    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
-    - docker login -u $CI_DEPENDENCY_PROXY_USER -p $CI_DEPENDENCY_PROXY_PASSWORD $CI_DEPENDENCY_PROXY_SERVER
-  needs: []
+    stage: build
+    image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/docker:latest
+    services:
+        - name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/docker:latest
+          alias: docker
+    before_script:
+        - apk add git
+        - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+        - docker login -u $CI_DEPENDENCY_PROXY_USER -p $CI_DEPENDENCY_PROXY_PASSWORD $CI_DEPENDENCY_PROXY_SERVER
 
 build-kms:
-  script:
-    - docker build -t kms -f kms/Dockerfile .
-  <<: *build
+    script:
+        - docker build -t kms -f kms/Dockerfile .
+    dependencies:
+        - generate-api
+    <<: *build
 
 build-ctrl:
-  script:
-    - docker build -t ctrl -f ctrl/Dockerfile .
-  <<: *build
+    script:
+        - docker build -t ctrl -f ctrl/Dockerfile .
+    dependencies:
+        - generate-api
+    <<: *build
diff --git a/.golangci.yaml b/.golangci.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..43c27a6206c097c68c6c5cd67f44025a54ff1c50
--- /dev/null
+++ b/.golangci.yaml
@@ -0,0 +1,28 @@
+version: "2"
+
+linters:
+    default: standard
+
+formatters:
+    # Enable specific formatter.
+    # Default: [] (uses standard Go formatting)
+    enable:
+        - gci
+        - gofmt
+        - gofumpt
+        - goimports
+        - golines
+
+issues:
+    # Maximum issues count per one linter.
+    # Set to 0 to disable.
+    # Default: 50
+    max-issues-per-linter: 0
+    # Maximum count of issues with the same text.
+    # Set to 0 to disable.
+    # Default: 3
+    max-same-issues: 0
+    fix: true
+
+run:
+    timeout: 5m
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 16b2ab1f520ea4e9c4638dcfc8041556128d8234..0000000000000000000000000000000000000000
--- a/Makefile
+++ /dev/null
@@ -1,4 +0,0 @@
-
-generate-plantuml:
-	@echo "Generating PlantUML diagrams if plantUML is installed..."
-	plantuml -o . api/_Docs/*/**.plantuml
\ No newline at end of file
diff --git a/README.md b/README.md
index b3657ee867ff12ad143cc08daa2344f5ff995e1f..05f34478cf2e810fceb74f784f68310d0ee4f715 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,9 @@ If you are interested in production grade QKDN software, please feel free to con
 ## Overview
 
 This repository is separated into different parts, mainly API definitions and code.
-We follow a SDN approach which is *controller centric*.
+We follow a SDN approach which is _controller centric_.
 This means that a central `QKDN-Controller` is responsible to manage and configure the network.
 
 - [API v1](api/_Docs/v1/Readme.md)
+- [Logging Guide](docs/Logging.md)
+- [Tracing Guide](docs/Tracing.md)
diff --git a/api/_Docs/v1/gfx/forwarding_complete.plantuml b/api/_Docs/v1/gfx/forwarding_complete.plantuml
index 340c1359737a2ece1fd3a7b6a757ddfbd46fc724..abbc3206fa9ea73e7e6c8832291df7c99a018cf7 100644
--- a/api/_Docs/v1/gfx/forwarding_complete.plantuml
+++ b/api/_Docs/v1/gfx/forwarding_complete.plantuml
@@ -32,4 +32,4 @@ KMS1 <-- QKDNController : AnnouncePayloadRelayResponse()
 User1 <-- KMS1 : PayloadExchangeResponse()
 KMS3 -> User2 : PayloadExchange()
 
-@enduml
\ No newline at end of file
+@enduml
diff --git a/api/_Docs/v1/gfx/forwarding_complete.png b/api/_Docs/v1/gfx/forwarding_complete.png
index 2a907643d8f89162318408b8cb8c6fac7629d88f..ff4aae61baaac836b62117ad8b550a719823c9e3 100644
Binary files a/api/_Docs/v1/gfx/forwarding_complete.png and b/api/_Docs/v1/gfx/forwarding_complete.png differ
diff --git a/api/_Docs/v1/gfx/key_sync.png b/api/_Docs/v1/gfx/key_sync.png
index 2b2c8102b8622efefe5b4fae458f2c37ebb19090..743674e7f3be3ac52ebe9e150181b4260a22613a 100644
Binary files a/api/_Docs/v1/gfx/key_sync.png and b/api/_Docs/v1/gfx/key_sync.png differ
diff --git a/api/ctrl/v1/health.proto b/api/ctrl/v1/health.proto
new file mode 100644
index 0000000000000000000000000000000000000000..50cccfd7964330e0bb8da11b7f69c8efc3b66e8a
--- /dev/null
+++ b/api/ctrl/v1/health.proto
@@ -0,0 +1,14 @@
+syntax = "proto3";
+package ctrl.v1;
+
+service HealthCtrl {
+  rpc Health(HealthRequest) returns (HealthResponse);
+}
+
+message HealthRequest {
+  string Message = 1;
+}
+
+message HealthResponse {
+  string Message = 1;
+}
diff --git a/api/kms/v1/health.proto b/api/kms/v1/health.proto
new file mode 100644
index 0000000000000000000000000000000000000000..c3d81907163c02662c404a7b4708ef4aba6d5853
--- /dev/null
+++ b/api/kms/v1/health.proto
@@ -0,0 +1,14 @@
+syntax = "proto3";
+package kms.v1;
+
+service HealthKms {
+  rpc Health(HealthKmsRequest) returns (HealthKmsResponse);
+}
+
+message HealthKmsRequest {
+  string Message = 1;
+}
+
+message HealthKmsResponse {
+  string Message = 1;
+}
diff --git a/ctrl/cmd/main.go b/ctrl/cmd/main.go
index f06940d7cfad9bc3a560e16bbfeceada4c248261..ecd9eb6d71c3e14e3b36e127ff8e6bc9d333ea98 100644
--- a/ctrl/cmd/main.go
+++ b/ctrl/cmd/main.go
@@ -1,26 +1,104 @@
 package main
 
 import (
+	"context"
 	"flag"
-	"fmt"
+	"net"
+	"runtime/debug"
 
 	config "code.fbi.h-da.de/danet/costaquanta/ctrl/internal"
+	"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/core/application"
+	pb "code.fbi.h-da.de/danet/costaquanta/gen/go/ctrl/v1"
+	"code.fbi.h-da.de/danet/costaquanta/libs/logging"
+	"code.fbi.h-da.de/danet/costaquanta/libs/tracing"
+	"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery"
+	"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
 )
 
 func main() {
+	log := logging.CreateProductionLogger("kms")
+	defer func() {
+		_ = log.Sync()
+	}()
+	logs := log.Sugar()
+
 	var bindAddr string
 
 	flag.StringVar(
 		&bindAddr,
 		"bindaddr",
 		"127.0.0.1:1337",
-		"default adress and port to bind the controller to",
+		"default adress and port to bind the controller interface to",
 	)
 
 	flag.Parse()
 
 	cfg := config.GetConfig()
 
-	fmt.Printf("%+v\n", cfg)
-	fmt.Printf("Binding to address %s", bindAddr)
+	logs.Infof("config: %+v", cfg)
+	logs.Infof("binding to address %s", bindAddr)
+
+	listen, err := net.Listen("tcp", bindAddr)
+	if err != nil {
+		logs.Fatalf("failed to listen: %v", err)
+	}
+
+	tracing.SetRuntimeSettings("kms")
+	traceProvider, err := tracing.GetTracer(context.Background(), tracing.Backend)
+	if err != nil {
+		logs.Fatal(err)
+	}
+	defer func() {
+		if err := traceProvider.Shutdown(context.Background()); err != nil {
+			logs.Infof("Error shutting down tracer provider: %v", err)
+		}
+	}()
+
+	grpcPanicRecoveryHandler := func(p any) (err error) {
+		logs.Errorf("recovered from gRPC panic %+v; %v", p, debug.Stack())
+
+		return status.Errorf(codes.Internal, "%s", p)
+	}
+
+	// var opts []grpc.ServerOption
+	//	if *tls {
+	//		if *certFile == "" {
+	//			*certFile = data.Path("x509/server_cert.pem")
+	//		}
+	//		if *keyFile == "" {
+	//			*keyFile = data.Path("x509/server_key.pem")
+	//		}
+	//		creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
+	//		if err != nil {
+	//			log.Fatalf("Failed to generate credentials: %v", err)
+	//		}
+	//		opts = []grpc.ServerOption{grpc.Creds(creds)}
+	//	}
+	// grpcServer := grpc.NewServer(opts...)
+
+	grpcServer := grpc.NewServer(
+		grpc.StatsHandler(otelgrpc.NewServerHandler()),
+		grpc.ChainUnaryInterceptor(
+			//	srvMetrics.UnaryServerInterceptor(grpcprom.WithExemplarFromContext(exemplarFromContext)),
+			//	logging.UnaryServerInterceptor(interceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID)),
+			//	selector.UnaryServerInterceptor(auth.UnaryServerInterceptor(authFn), selector.MatchFunc(allButHealthZ)),
+			recovery.UnaryServerInterceptor(recovery.WithRecoveryHandler(grpcPanicRecoveryHandler)),
+		),
+		grpc.ChainStreamInterceptor(
+			//	srvMetrics.StreamServerInterceptor(grpcprom.WithExemplarFromContext(exemplarFromContext)),
+			//	logging.StreamServerInterceptor(interceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID)),
+			//	selector.StreamServerInterceptor(auth.StreamServerInterceptor(authFn), selector.MatchFunc(allButHealthZ)),
+			recovery.StreamServerInterceptor(
+				recovery.WithRecoveryHandler(grpcPanicRecoveryHandler),
+			),
+		),
+	)
+
+	app := application.NewApplication(log, traceProvider)
+
+	pb.RegisterHealthCtrlServer(grpcServer, app.HealthServer)
+	_ = grpcServer.Serve(listen)
 }
diff --git a/ctrl/internal/core/application/app.go b/ctrl/internal/core/application/app.go
new file mode 100644
index 0000000000000000000000000000000000000000..17cbf6e2643bcfd23f3369944b03eaa32f878c30
--- /dev/null
+++ b/ctrl/internal/core/application/app.go
@@ -0,0 +1,24 @@
+package application
+
+import (
+	"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/infrastructure/interaction"
+	"code.fbi.h-da.de/danet/costaquanta/libs/logging"
+	sdktrace "go.opentelemetry.io/otel/sdk/trace"
+	"go.uber.org/zap"
+)
+
+type Application struct {
+	tracer       *sdktrace.TracerProvider
+	HealthServer *interaction.HealthServer
+}
+
+func NewApplication(log *zap.Logger, tracer *sdktrace.TracerProvider) *Application {
+	healthServerLogger := logging.CreateChildLogger(log, "healthServer")
+	childTracer := tracer.Tracer("healthServer")
+	healthSrv := interaction.NewHealthServer(healthServerLogger, childTracer)
+
+	return &Application{
+		HealthServer: healthSrv,
+		tracer:       tracer,
+	}
+}
diff --git a/ctrl/internal/infrastructure/interaction/health.go b/ctrl/internal/infrastructure/interaction/health.go
new file mode 100644
index 0000000000000000000000000000000000000000..ffb554c87997ea8c5454a0042c9c397dfd1eb6af
--- /dev/null
+++ b/ctrl/internal/infrastructure/interaction/health.go
@@ -0,0 +1,39 @@
+package interaction
+
+import (
+	"context"
+
+	pb "code.fbi.h-da.de/danet/costaquanta/gen/go/ctrl/v1"
+	"go.opentelemetry.io/otel/trace"
+	"go.uber.org/zap"
+)
+
+type HealthServer struct {
+	pb.UnimplementedHealthCtrlServer
+
+	logger *zap.SugaredLogger
+	tracer trace.Tracer
+}
+
+func (h *HealthServer) Health(
+	ctx context.Context,
+	request *pb.HealthRequest,
+) (*pb.HealthResponse, error) {
+	h.logger.Debugf("got health request %+v", request)
+
+	_, span := h.tracer.Start(ctx, "Health")
+	defer span.End()
+
+	return &pb.HealthResponse{
+		Message: request.Message,
+	}, nil
+}
+
+func NewHealthServer(logger *zap.SugaredLogger, tracer trace.Tracer) *HealthServer {
+	s := &HealthServer{
+		logger: logger,
+		tracer: tracer,
+	}
+
+	return s
+}
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..393d21033ca9fd6f8de7a8b6a1fd7b46deb73fb5
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,9 @@
+services:
+  jaeger:
+    image: jaegertracing/all-in-one:latest
+    environment:
+      COLLECTOR_OTLP_ENABLED: true
+    ports:
+      - 127.0.0.1:16686:16686
+      - 127.0.0.1:4317:4317
+      - 127.0.0.1:4318:4318
diff --git a/docs/Logging.md b/docs/Logging.md
new file mode 100644
index 0000000000000000000000000000000000000000..343596b9292beb756587ad70e68bdf9c22814f17
--- /dev/null
+++ b/docs/Logging.md
@@ -0,0 +1,65 @@
+# Logging
+
+We use [zap](https://github.com/uber-go/zap) as primary logging library.
+It is [fast](https://github.com/uber-go/zap?tab=readme-ov-file#performance), provides structured logging out of the box and is convinent to use.
+
+## How to use zap
+
+We are (at least for now) defaulting to the suggared logger, so we get structured logs automatically, but don't have to manually configure every log
+call.
+
+In the following are a few examples how to use zap:
+
+```go
+log := zap.Must(zap.NewProduction())
+
+defer log.Sync()
+
+logger := log.Sugar()
+
+
+sugar.Infof(
+    "Hello from Zap at %s",
+    time.Now().Format("03:04 AM"),
+)
+
+sugar.Infow("User logged in",
+    "username", "johndoe",
+    "userid", 123456,
+    zap.String("provider", "google"),
+)
+```
+
+## Logger lib
+
+To make sure all components in the monorepo use the same logger settings,
+we created a centralized logger lib in `libs/logger`.
+
+## Rules
+
+1. Each component **must** use the provided logger library.
+2. Every service in a component **must** create it's own child logger.
+   This will make debugging much easier, as it can be seen at a glance which
+   component and which service in that component emitted the log.
+
+    ```go
+    log := logger.CreateProductionLogger("kms")
+    defer log.Sync()
+    logs := log.Sugar()
+
+    healthServerLogger := logger.CreateChildLogger(log, "healthServer")
+    healthServerLogger.Info("Hello")
+    ```
+
+    will result in the following log statement:
+
+    ```json
+    {
+        "level": "info",
+        "timestamp": "2025-03-31T08:50:06.921+0200",
+        "caller": "cmd/main.go:37",
+        "msg": "Hello",
+        "component": "kms",
+        "service": "healthServer"
+    }
+    ```
diff --git a/docs/Tracing.md b/docs/Tracing.md
new file mode 100644
index 0000000000000000000000000000000000000000..4f20d4313fe08a646a2fcf5dbfd53679600632d5
--- /dev/null
+++ b/docs/Tracing.md
@@ -0,0 +1,44 @@
+# Tracing
+
+We use [OpenTelemetry](https://opentelemetry.io/) to instrument our applications
+with tracing support.
+It was an deliberate decision to don't [auto-instrument](https://github.com/open-telemetry/opentelemetry-go-instrumentation/tree/main)
+the applications to have more control about what flows must be instrumented.
+
+## Backend
+
+The current implementation supports two different exporters targeting two different
+backends to export spans to.
+At first there is the **Stdout** exporter, which exports spans to stdout.
+The more interesting exporter is the **Backend** exporter, which can export
+spans into external tools like [Jaeger](https://www.jaegertracing.io/).
+
+The exporter mode must be configured during set up of the tracer:
+
+```go
+traceProvider, err := tracing.Init(tracing.Stdout)
+	or
+traceProvider, err := tracing.Init(tracing.Backend)
+```
+
+This setup includes a all in one Jaeger instance that can be started with
+`docker compose up -d` and then reached via [localhost:16686/search](http://localhost:16686/search).
+
+## Configuration
+
+To be able to work with traces a few specific configurations are needed for otel
+to properly work and be useful at the end.
+Therefore we introduced the `SetRuntimeSettings` method in the lib.
+It makes sure to set for example the service name to be be able to visually
+differentiate spans in a trace.
+
+## Testing
+
+The KMS is instrumented with tracing support on its `HealthKms/Health` service.
+For now the traces are just emitted on stdout until we decided for a common
+trace backend.
+
+```sh
+grpcurl -plaintext -import-path api/ -proto api/kms/v1/health.proto localhost:1337 kms.v1.HealthKms/Health
+
+```
diff --git a/go.mod b/go.mod
index bc042e58eed559848946e325c820f0fcd9f84703..5b33b9adeff6da8d81b55b777528810b71299e6f 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,36 @@ module code.fbi.h-da.de/danet/costaquanta
 
 go 1.24
 
-require github.com/caarlos0/env/v11 v11.3.1
+require (
+	github.com/caarlos0/env/v11 v11.3.1
+	github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0
+	go.opentelemetry.io/otel v1.35.0
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0
+	go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0
+	go.opentelemetry.io/otel/sdk v1.35.0
+	go.opentelemetry.io/otel/trace v1.35.0
+	go.uber.org/zap v1.27.0
+	google.golang.org/grpc v1.71.0
+	google.golang.org/protobuf v1.36.5
+)
+
+require (
+	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+	github.com/felixge/httpsnoop v1.0.4 // indirect
+	github.com/go-logr/logr v1.4.2 // indirect
+	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
+	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
+	go.opentelemetry.io/otel/metric v1.35.0 // indirect
+	go.opentelemetry.io/proto/otlp v1.5.0 // indirect
+	go.uber.org/multierr v1.11.0 // indirect
+	golang.org/x/net v0.35.0 // indirect
+	golang.org/x/sys v0.30.0 // indirect
+	golang.org/x/text v0.22.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
+)
diff --git a/go.sum b/go.sum
index 1724948ebb6c354479037ecb2ccb4ccd25f4c3f0..34518bdc687361222561bf7619776bd786e946d4 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,67 @@
 github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
 github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
+github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1 h1:KcFzXwzM/kGhIRHvc8jdixfIJjVzuUJdnv+5xsPutog=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
+go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
+go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 h1:T0Ec2E+3YZf5bgTNQVet8iTDW7oIk03tXHq+wkwIDnE=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0/go.mod h1:30v2gqH+vYGJsesLWFov8u47EpYTcIQcBjKpI6pJThg=
+go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
+go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
+go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
+go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
+go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
+go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
+go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
+go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
+golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
+golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
+golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
+golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
+google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
+google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
+google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
+google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
+google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
diff --git a/justfile b/justfile
new file mode 100644
index 0000000000000000000000000000000000000000..fe7514a1292fc42596127917d37acc75985034a0
--- /dev/null
+++ b/justfile
@@ -0,0 +1,29 @@
+golangci-lint-version := "v2.0.2"
+tools-install-path := "bin"
+
+run-kms:
+    go run kms/cmd/main.go
+
+run-ctrl:
+    go run ctrl/cmd/main.go
+
+build-api:
+    buf generate
+
+lint:
+    docker run --rm -t -v $(pwd):/app -w /app \
+    --user $(id -u):$(id -g) \
+    -v $(go env GOCACHE):/.cache/go-build -e GOCACHE=/.cache/go-build \
+    -v $(go env GOMODCACHE):/.cache/mod -e GOMODCACHE=/.cache/mod \
+    -v ~/.cache/golangci-lint:/.cache/golangci-lint -e GOLANGCI_LINT_CACHE=/.cache/golangci-lint \
+    golangci/golangci-lint:{{ golangci-lint-version }} golangci-lint run
+
+ci-lint:
+    mkdir -p {{ tools-install-path }}
+    export GOBIN={{ tools-install-path }}
+    curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s {{ golangci-lint-version }}
+    ./{{ tools-install-path }}/golangci-lint run
+
+generate-plantuml:
+	echo "Generating PlantUML diagrams if plantUML is installed..."
+	plantuml -o . api/_Docs/*/**.plantuml
diff --git a/kms/cmd/main.go b/kms/cmd/main.go
index b6cd3a5e6ae5141030c07b00484bb723e287c8eb..37392a437938ba79632894c5bce49ed7e251709b 100644
--- a/kms/cmd/main.go
+++ b/kms/cmd/main.go
@@ -1,13 +1,30 @@
 package main
 
 import (
+	"context"
 	"flag"
-	"fmt"
+	"net"
+	"runtime/debug"
 
+	pb "code.fbi.h-da.de/danet/costaquanta/gen/go/kms/v1"
 	config "code.fbi.h-da.de/danet/costaquanta/kms/internal"
+	"code.fbi.h-da.de/danet/costaquanta/kms/internal/core/application"
+	"code.fbi.h-da.de/danet/costaquanta/libs/logging"
+	"code.fbi.h-da.de/danet/costaquanta/libs/tracing"
+	"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery"
+	"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
 )
 
 func main() {
+	log := logging.CreateProductionLogger("kms")
+	defer func() {
+		_ = log.Sync()
+	}()
+	logs := log.Sugar()
+
 	var bindAddr string
 	var ctrlAddr string
 
@@ -28,6 +45,67 @@ func main() {
 
 	cfg := config.GetConfig()
 
-	fmt.Printf("%+v\n", cfg)
-	fmt.Printf("Binding to address %s", bindAddr)
+	logs.Infof("config: %+v", cfg)
+	logs.Infof("binding to address %s", bindAddr)
+
+	listen, err := net.Listen("tcp", bindAddr)
+	if err != nil {
+		logs.Fatalf("failed to listen: %v", err)
+	}
+
+	tracing.SetRuntimeSettings("kms")
+	traceProvider, err := tracing.GetTracer(context.Background(), tracing.Backend)
+	if err != nil {
+		logs.Fatal(err)
+	}
+	defer func() {
+		if err := traceProvider.Shutdown(context.Background()); err != nil {
+			logs.Infof("Error shutting down tracer provider: %v", err)
+		}
+	}()
+
+	grpcPanicRecoveryHandler := func(p any) (err error) {
+		logs.Errorf("recovered from gRPC panic %+v; %v", p, debug.Stack())
+
+		return status.Errorf(codes.Internal, "%s", p)
+	}
+
+	// var opts []grpc.ServerOption
+	//	if *tls {
+	//		if *certFile == "" {
+	//			*certFile = data.Path("x509/server_cert.pem")
+	//		}
+	//		if *keyFile == "" {
+	//			*keyFile = data.Path("x509/server_key.pem")
+	//		}
+	//		creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
+	//		if err != nil {
+	//			log.Fatalf("Failed to generate credentials: %v", err)
+	//		}
+	//		opts = []grpc.ServerOption{grpc.Creds(creds)}
+	//	}
+	// grpcServer := grpc.NewServer(opts...)
+
+	grpcServer := grpc.NewServer(
+		grpc.StatsHandler(otelgrpc.NewServerHandler()),
+		grpc.ChainUnaryInterceptor(
+			//	srvMetrics.UnaryServerInterceptor(grpcprom.WithExemplarFromContext(exemplarFromContext)),
+			//	logging.UnaryServerInterceptor(interceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID)),
+			//	selector.UnaryServerInterceptor(auth.UnaryServerInterceptor(authFn), selector.MatchFunc(allButHealthZ)),
+			recovery.UnaryServerInterceptor(recovery.WithRecoveryHandler(grpcPanicRecoveryHandler)),
+		),
+		grpc.ChainStreamInterceptor(
+			//	srvMetrics.StreamServerInterceptor(grpcprom.WithExemplarFromContext(exemplarFromContext)),
+			//	logging.StreamServerInterceptor(interceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID)),
+			//	selector.StreamServerInterceptor(auth.StreamServerInterceptor(authFn), selector.MatchFunc(allButHealthZ)),
+			recovery.StreamServerInterceptor(
+				recovery.WithRecoveryHandler(grpcPanicRecoveryHandler),
+			),
+		),
+	)
+
+	app := application.NewApplication(log, traceProvider)
+
+	pb.RegisterHealthKmsServer(grpcServer, app.HealthServer)
+	_ = grpcServer.Serve(listen)
 }
diff --git a/kms/internal/core/application/app.go b/kms/internal/core/application/app.go
new file mode 100644
index 0000000000000000000000000000000000000000..eac591d4b97973e7b2cdabba6bc50a2fc01567d5
--- /dev/null
+++ b/kms/internal/core/application/app.go
@@ -0,0 +1,24 @@
+package application
+
+import (
+	"code.fbi.h-da.de/danet/costaquanta/kms/internal/infrastructure/interaction"
+	"code.fbi.h-da.de/danet/costaquanta/libs/logging"
+	sdktrace "go.opentelemetry.io/otel/sdk/trace"
+	"go.uber.org/zap"
+)
+
+type Application struct {
+	tracer       *sdktrace.TracerProvider
+	HealthServer *interaction.HealthServer
+}
+
+func NewApplication(log *zap.Logger, tracer *sdktrace.TracerProvider) *Application {
+	healthServerLogger := logging.CreateChildLogger(log, "healthServer")
+	childTracer := tracer.Tracer("healthServer")
+	healthSrv := interaction.NewHealthServer(healthServerLogger, childTracer)
+
+	return &Application{
+		HealthServer: healthSrv,
+		tracer:       tracer,
+	}
+}
diff --git a/kms/internal/core/ports/interaction.go b/kms/internal/core/ports/interaction.go
new file mode 100644
index 0000000000000000000000000000000000000000..808de880458d99a7446599504e73a6c7ff0c94dc
--- /dev/null
+++ b/kms/internal/core/ports/interaction.go
@@ -0,0 +1 @@
+package ports
diff --git a/kms/internal/infrastructure/interaction/health.go b/kms/internal/infrastructure/interaction/health.go
new file mode 100644
index 0000000000000000000000000000000000000000..d97bbe7d2a2847ef2c36ce8f000c65b423a1b91e
--- /dev/null
+++ b/kms/internal/infrastructure/interaction/health.go
@@ -0,0 +1,39 @@
+package interaction
+
+import (
+	"context"
+
+	pb "code.fbi.h-da.de/danet/costaquanta/gen/go/kms/v1"
+	"go.opentelemetry.io/otel/trace"
+	"go.uber.org/zap"
+)
+
+type HealthServer struct {
+	pb.UnimplementedHealthKmsServer
+
+	logger *zap.SugaredLogger
+	tracer trace.Tracer
+}
+
+func (h *HealthServer) Health(
+	ctx context.Context,
+	request *pb.HealthKmsRequest,
+) (*pb.HealthKmsResponse, error) {
+	h.logger.Debugf("got health request %+v", request)
+
+	_, span := h.tracer.Start(ctx, "Health")
+	defer span.End()
+
+	return &pb.HealthKmsResponse{
+		Message: request.Message,
+	}, nil
+}
+
+func NewHealthServer(logger *zap.SugaredLogger, tracer trace.Tracer) *HealthServer {
+	s := &HealthServer{
+		logger: logger,
+		tracer: tracer,
+	}
+
+	return s
+}
diff --git a/libs/logging/logging.go b/libs/logging/logging.go
new file mode 100644
index 0000000000000000000000000000000000000000..e195799807f22c66480c20133d5e2943d6db597a
--- /dev/null
+++ b/libs/logging/logging.go
@@ -0,0 +1,41 @@
+package logging
+
+import (
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+func CreateProductionLogger(component string) *zap.Logger {
+	encoderCfg := zap.NewProductionEncoderConfig()
+	encoderCfg.TimeKey = "timestamp"
+	encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder
+
+	config := zap.Config{
+		Level:             zap.NewAtomicLevelAt(zap.InfoLevel),
+		Development:       false,
+		DisableCaller:     false,
+		DisableStacktrace: false,
+		Sampling:          nil,
+		Encoding:          "json",
+		EncoderConfig:     encoderCfg,
+		OutputPaths: []string{
+			"stderr",
+		},
+		ErrorOutputPaths: []string{
+			"stderr",
+		},
+		InitialFields: map[string]interface{}{
+			"component": component,
+		},
+	}
+
+	return zap.Must(config.Build())
+}
+
+func CreateChildLogger(logger *zap.Logger, service string) *zap.SugaredLogger {
+	childLogger := logger.With(
+		zap.String("service", service),
+	)
+
+	return childLogger.Sugar()
+}
diff --git a/libs/tracing/tracing.go b/libs/tracing/tracing.go
new file mode 100644
index 0000000000000000000000000000000000000000..87c49326c84a6c452a30114b068e51c25ecf01da
--- /dev/null
+++ b/libs/tracing/tracing.go
@@ -0,0 +1,61 @@
+package tracing
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"go.opentelemetry.io/otel"
+	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
+	stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
+	"go.opentelemetry.io/otel/propagation"
+	sdktrace "go.opentelemetry.io/otel/sdk/trace"
+)
+
+type TraceExportTarget int
+
+const (
+	Stdout TraceExportTarget = iota
+	Backend
+)
+
+func GetTracer(ctx context.Context, target TraceExportTarget) (*sdktrace.TracerProvider, error) {
+	var exporter sdktrace.SpanExporter
+	var err error
+
+	switch target {
+	case Stdout:
+		exporter, err = stdout.New(stdout.WithPrettyPrint())
+		if err != nil {
+			return nil, err
+		}
+	case Backend:
+		exporter, err = otlptracehttp.New(
+			ctx,
+			otlptracehttp.WithInsecure(),
+			otlptracehttp.WithEndpoint("127.0.0.1:4318"),
+		)
+		if err != nil {
+			return nil, fmt.Errorf("failed to create trace exporter: %w", err)
+		}
+
+	}
+
+	tp := sdktrace.NewTracerProvider(
+		sdktrace.WithSampler(sdktrace.AlwaysSample()),
+		sdktrace.WithBatcher(exporter),
+	)
+	otel.SetTracerProvider(tp)
+	otel.SetTextMapPropagator(
+		propagation.NewCompositeTextMapPropagator(
+			propagation.TraceContext{},
+			propagation.Baggage{},
+		),
+	)
+
+	return tp, nil
+}
+
+func SetRuntimeSettings(serviceName string) {
+	_ = os.Setenv("OTEL_SERVICE_NAME", serviceName)
+}