diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..97d23d4d613b86347c8abc54c8971718606a33af
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,66 @@
+image: containers.ethitter.com:443/docker/images/golang:latest
+
+variables:
+  REPO_NAME: git.ethitter.com/open-source/dyndnsd-client
+
+cache:
+  paths:
+    - /apt-cache
+    - $GOPATH/src/github.com
+    - $GOPATH/src/golang.org
+    - $GOPATH/src/google.golang.org
+    - $GOPATH/src/gopkg.in
+
+stages:
+  - test
+  - build
+
+before_script:
+  - mkdir -p $GOPATH/src/$(dirname $REPO_NAME)
+  - cp -R $CI_PROJECT_DIR $GOPATH/src/$REPO_NAME
+  - cd $GOPATH/src/$REPO_NAME
+  - cp config-sample.json config.json
+
+  - export CC=clang-5.0
+
+  - make dep
+
+unit_tests:
+  stage: test
+  script:
+    - make test
+
+race_detector:
+  stage: test
+  script:
+    - make race
+
+memory_sanitizer:
+  stage: test
+  script:
+    - make msan
+
+code_coverage:
+  stage: test
+  script:
+    - make coverage
+
+code_coverage_report:
+  stage: test
+  script:
+    - make coverhtml
+  only:
+  - master
+
+lint_code:
+  stage: test
+  script:
+    - make lint
+
+build:
+  stage: build
+  script:
+    - make
+  artifacts:
+    paths:
+      - dyndnsd-client/
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..bcb249972c4443bf82ac452084d359fed73bb146
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+PROJECT_NAME := "dyndnsd-client"
+PKG := "git.ethitter.com/debian/$(PROJECT_NAME)"
+PKG_LIST := $(shell go list ${PKG}/... | grep -v /vendor/)
+GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/ | grep -v _test.go)
+
+.PHONY: all dep build clean test coverage coverhtml lint
+
+all: build
+
+lint:
+	@golint -set_exit_status ${PKG_LIST}
+
+test:
+	@go test -v ${PKG_LIST}
+
+race: dep
+	@go test -v -race ${PKG_LIST}
+
+msan: dep
+	@go test -v -msan ${PKG_LIST}
+
+coverage:
+	./tools/coverage.sh;
+
+coverhtml:
+	./tools/coverage.sh html;
+
+dep:
+	@go get -v -d ./...
+	@go get github.com/mitchellh/gox
+
+build: dep
+	@gox -output="${CI_PROJECT_DIR}/${PROJECT_NAME}/{{.Dir}}_{{.OS}}_{{.Arch}}" -parallel=6
+
+clean:
+	@rm -f $(PROJECT_NAME)
+
+help:
+	@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
diff --git a/tools/coverage.sh b/tools/coverage.sh
new file mode 100755
index 0000000000000000000000000000000000000000..0afd4f114465980f501559be19b79ac2ad39fe7f
--- /dev/null
+++ b/tools/coverage.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# Code coverage generation
+
+COVERAGE_DIR="${COVERAGE_DIR:-coverage}"
+PKG_LIST=$(go list ./... | grep -v /vendor/)
+
+# Create the coverage files directory
+mkdir -p "$COVERAGE_DIR";
+
+# Create a coverage file for each package
+for package in ${PKG_LIST}; do
+    go test -covermode=count -coverprofile "${COVERAGE_DIR}/${package##*/}.cov" "$package" ;
+done ;
+
+# Merge the coverage profile files
+echo 'mode: count' > "${COVERAGE_DIR}"/coverage.cov ;
+tail -q -n +2 "${COVERAGE_DIR}"/*.cov >> "${COVERAGE_DIR}"/coverage.cov ;
+
+# Display the global code coverage
+go tool cover -func="${COVERAGE_DIR}"/coverage.cov ;
+
+# If needed, generate HTML report
+if [ "$1" == "html" ]; then
+    go tool cover -html="${COVERAGE_DIR}"/coverage.cov -o coverage.html ;
+fi
+
+# Remove the coverage files directory
+rm -rf "$COVERAGE_DIR";