diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c7b122572752ef38fea0fd92c05b064202189f9d..9f461f1a2dc4d80715184602028357e7209d6eff 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,6 @@
 stages:
   - build
+  - code-quality
 
 variables:
   IMAGE_PATH: "${CI_REGISTRY_IMAGE}"
@@ -21,14 +22,25 @@ variables:
 
 build-ekms:
     script:
-        - IMAGE_NAME="$IMAGE_PATH/ekms"
-        - TAG=$CI_COMMIT_REF_SLUG
-        - docker buildx build --push -t "$IMAGE_NAME:$TAG" -f ekms/Dockerfile --build-arg "GITLAB_PROXY=${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/" --build-arg GITLAB_LOGIN=${GITLAB_LOGIN} --build-arg GITLAB_TOKEN=${GITLAB_TOKEN} --build-arg GOLANG_VERSION=${GOLANG_VERSION} .
+      - IMAGE_NAME="$IMAGE_PATH/ekms"
+      - TAG=$CI_COMMIT_REF_SLUG
+      - docker buildx build --push -t "$IMAGE_NAME:$TAG" -f ekms/Dockerfile --build-arg "GITLAB_PROXY=${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/" --build-arg GITLAB_LOGIN=${GITLAB_LOGIN} --build-arg GITLAB_TOKEN=${GITLAB_TOKEN} --build-arg GOLANG_VERSION=${GOLANG_VERSION} .
     <<: *build
 
 build-quantumlayer:
     script:
-        - IMAGE_NAME="$IMAGE_PATH/quantumlayer"
-        - TAG=$CI_COMMIT_REF_SLUG
-        - docker buildx build --push -t "$IMAGE_NAME:$TAG" -f quantumlayer/Dockerfile --build-arg "GITLAB_PROXY=${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/" --build-arg GITLAB_LOGIN=${GITLAB_LOGIN} --build-arg GITLAB_TOKEN=${GITLAB_TOKEN} --build-arg GOLANG_VERSION=${GOLANG_VERSION} .
+      - IMAGE_NAME="$IMAGE_PATH/quantumlayer"
+      - TAG=$CI_COMMIT_REF_SLUG
+      - docker buildx build --push -t "$IMAGE_NAME:$TAG" -f quantumlayer/Dockerfile --build-arg "GITLAB_PROXY=${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/" --build-arg GITLAB_LOGIN=${GITLAB_LOGIN} --build-arg GITLAB_TOKEN=${GITLAB_TOKEN} --build-arg GOLANG_VERSION=${GOLANG_VERSION} .
     <<: *build
+
+
+
+lint:
+    stage: code-quality
+    image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/golangci/golangci-lint:v1.55.2-alpine
+    script:
+      # writes golangci-lint output to gl-code-quality-report.json
+      - apk add --update make jq
+      - make ci-lint
+    needs: []
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..23fdaf7ea5e24b760cbf8952694a04085e41205e
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,76 @@
+variables:
+    GOLANG_VERSION: "1.21"
+run:
+    go: $GOLANG_VERSION
+    concurrency: 8
+    timeout: 20m
+    issues-exit-code: 1
+    # directories to be ignored by linters
+    skip-dirs:
+        - .git/
+        - ekms/model
+        - ekms/models
+        - artifacts/
+    skip-dirs-default: true
+    #skip-files:
+    #    - http.go
+    modules-download-mode: readonly
+
+# output settings -> code-climate for GitLab
+output:
+    format: code-climate
+    print-issued-lines: true
+    print-linter-name: true
+    uniq-by-line: true
+    path-prefix: ""
+
+issues:
+    exclude-use-default: false
+    max-issues-per-linter: 0
+    max-same-issues: 0
+
+linters:
+    # enable the specific needed linters
+    # see here for full list: https://golangci-lint.run/usage/linters/
+    # linters to consider: gosimple, containedctx, contextcheck, depguard, errchkjson, exhaustive, exhaustruct, forbidigo,
+    # gochecknoinits, gocognit, goconst, gocritic, gofumpt, gomnd, gosec, importas, lll, nestif, nilerr, nlreturn, noctx, nolintlint,
+    # nosnakecase, paralleltest, prealloc, structcheck, testpackage, tparallel, unparam, wastedassign, wrapcheck, wsl
+    disable-all: true
+    enable:
+        - gofmt
+        - goimports
+        - gocyclo
+        - govet
+        - unused
+        - staticcheck
+        - typecheck
+        - revive
+        - whitespace
+        - errcheck
+        - ineffassign
+        - bidichk
+        - durationcheck
+        - errorlint
+        - exportloopref
+        - grouper
+        - makezero
+        - misspell
+        - nilnil
+        - predeclared
+        - godot
+        - errname
+
+# custom settings for linters
+linters-settings:
+    gocyclo:
+        min-complexity: 15
+    golint:
+        min-confidence: 0.8
+    errcheck:
+        # Report about not checking of errors in type assertions: `a := b.(MyStruct)`.
+        # Such cases aren't reported by default.
+        # Default: false
+        check-type-assertions: true
+    revive:
+        severity: warning
+        confidence: 0.8
diff --git a/Makefile b/Makefile
index 1e7b4b39c5c85a7f16bb91b6524a37b058ac4399..37ef19b4f777d6dfd92f8065d409e88a1297bd1c 100644
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,9 @@ TOOLS_DIR:= build-tools
 GOSDN_PRG := $(MAKEFILE_DIR)$(TOOLS_DIR)
 GOPATH := $(~/go)
 GOBIN := $(GOSDN_PRG)
+
 GOLANG_VERSION := 1.21
+GOLANGCI_LINT_VERSION=v1.55.0
 
 GOCMD=go
 GOBUILD=$(GOCMD) build
@@ -24,10 +26,18 @@ pre:
 install-tools:
 	@echo Install development tooling
 	mkdir -p $(GOSDN_PRG)
-	export GOBIN=$(GOSDN_PRG) && go install github.com/openconfig/ygot/generator@v0.28.3 &&\
-	go install github.com/andresterba/go-ygot-generator-generator@v0.0.4
+	export GOBIN=$(GOSDN_PRG) &&\
+	go install github.com/openconfig/ygot/generator@v0.28.3 &&\
+	go install github.com/andresterba/go-ygot-generator-generator@v0.0.4 &&\
+	go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
 	@echo Finished installing development tooling
 
+lint: install-tools
+	./$(TOOLS_DIR)/golangci-lint run --config .golangci.yml | jq
+
+lint-fix: install-tools
+	./$(TOOLS_DIR)/golangci-lint run --config .golangci.yml --fix | jq
+
 build-ekms: pre
 	CGO_ENABLED=0 $(GOBUILD) -o $(BUILD_ARTIFACTS_PATH)/ekms ./ekms/main.go
 
@@ -88,3 +98,10 @@ clean:
 	rm -rf $(TOOLS_DIR)
 	$(GOCLEAN)
 	docker system prune -af
+
+
+ci-install-tools:
+	go install gotest.tools/gotestsum@$(GOTESTSUM_VERSION)
+
+ci-lint: ci-install-tools
+	golangci-lint run --config .golangci.yml --out-format code-climate