diff --git a/cli/cmd/login.go b/cli/cmd/login.go
index c6909d96dd0620cdd7c4a083aaf170841f9d83cc..ac210be646fd12778915ccf1b3429dcae938daa0 100644
--- a/cli/cmd/login.go
+++ b/cli/cmd/login.go
@@ -59,10 +59,7 @@ var loginCmd = &cobra.Command{
 
 		// log out to remove active session in case an user is already logged in
 		if userToken != "" {
-			_, err := api.Logout(createContextWithAuthorization(), viper.GetString("controllerAPIEndpoint"), nbUserName)
-			if err != nil {
-				pterm.Error.Println("error logging out active user", err)
-			}
+			_, _ = api.Logout(createContextWithAuthorization(), viper.GetString("controllerAPIEndpoint"), nbUserName)
 		}
 
 		// TODO: maybe add credentials in context instead of context.TODO()
diff --git a/controller/cmd/root.go b/controller/cmd/root.go
index 7276344020865de5d146d98f63f781005cbe905d..22f31befeb4ba4800556f11217506a4e4fda3e41 100644
--- a/controller/cmd/root.go
+++ b/controller/cmd/root.go
@@ -100,7 +100,7 @@ func initConfig() {
 	} else {
 		env := config.DetermineConfigEnvironment()
 
-		log.Infof("environment is %s\n", env)
+		log.Debugf("environment is %s\n", env)
 
 		viper.AddConfigPath(configHome)
 		viper.AddConfigPath("/usr/local/etc/gosdn/")
diff --git a/controller/cmd/version.go b/controller/cmd/version.go
new file mode 100644
index 0000000000000000000000000000000000000000..bc4902b31b1f7e61f78355b2446efe25243dafc6
--- /dev/null
+++ b/controller/cmd/version.go
@@ -0,0 +1,33 @@
+package cmd
+
+import (
+	"fmt"
+
+	"code.fbi.h-da.de/danet/gosdn/controller/version"
+	log "github.com/sirupsen/logrus"
+	"github.com/spf13/cobra"
+)
+
+// version represents the version command.
+var versionCmd = &cobra.Command{
+	Use:   "version",
+	Short: "returns information about the controllers version",
+	Long: `Version allows the user to access version and build information
+    about the current binary.`,
+
+	RunE: func(cmd *cobra.Command, args []string) error {
+		log.SetFormatter(&log.TextFormatter{
+			DisableQuote: true,
+		})
+		v, err := version.NewVersion()
+		if err != nil {
+			log.Error(err)
+		}
+		fmt.Println(v.String())
+		return nil
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(versionCmd)
+}
diff --git a/controller/version/version.go b/controller/version/version.go
new file mode 100644
index 0000000000000000000000000000000000000000..4b146dd9337b44a594226e0c1cc9d08afbe253ef
--- /dev/null
+++ b/controller/version/version.go
@@ -0,0 +1,173 @@
+package version
+
+import (
+	"fmt"
+	"runtime/debug"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var (
+	// The versionString is provided through ldflags while building. This
+	// variable should only be set through ldflags!
+	// Defaults to `0.0.0`.
+	versionString = "0.0.0"
+)
+
+// BuildInformation contains additional information about the build. The information
+// is taken from `debug.ReadBuildInfo`.
+type BuildInformation struct {
+	// goos describes the os the binary is built for.
+	goos string
+	// goarch describes the architecture the binary is built for.
+	goarch string
+	// revision is a reference to the last git commit the binary is based on.
+	revision string
+	// time is the timestamp of the last git commit the binary is based on.
+	time time.Time
+	// modified returns true if the build is modified and contains additional
+	// changes from the last commit - a so called dirty build.
+	modified bool
+}
+
+// Version describes the current version of the goSDN controller and contains
+// additional build information.
+type Version struct {
+	major int
+	minor int
+	patch int
+
+	BuildInformation
+}
+
+// NewVersion creates a new Version from the versionString, provided via
+// ldflags while running `go build`. If no version information is provided
+// through out the build process a default value is used.
+//
+// Additional build information is gathered with the help of
+// `debug.ReadBuildInfo` and is added to the Version.
+func NewVersion() (*Version, error) {
+	buildInfo, ok := debug.ReadBuildInfo()
+	if !ok {
+		return nil, fmt.Errorf("Failed reading the binaries build information")
+	}
+
+	parsedVersion, err := parseVersionString(versionString)
+	if err != nil {
+		return nil, fmt.Errorf("Could not parse version string: %w", err)
+	}
+
+	version := &Version{
+		major: parsedVersion[0],
+		minor: parsedVersion[1],
+		patch: parsedVersion[2],
+	}
+
+	for _, bSetting := range buildInfo.Settings {
+		switch bSetting.Key {
+		case "GOOS":
+			version.goos = bSetting.Value
+		case "GOARCH":
+			version.goarch = bSetting.Value
+		case "vcs.revision":
+			version.revision = bSetting.Value
+		case "vcs.time":
+			parsedTime, _ := time.Parse(time.RFC3339, bSetting.Value)
+			version.time = parsedTime
+		case "vcs.modified":
+			version.modified = bSetting.Value == "true"
+		}
+	}
+
+	return version, nil
+}
+
+func (v *Version) String() string {
+	return fmt.Sprintf(
+		`Version: %d.%d.%d
+Build Information:
+    - GOOS: %s
+    - GOARCH: %s
+    - Revision: %s
+    - Time: %s
+    - Modified (dirty): %t`,
+		v.major, v.minor, v.patch, v.goos, v.goarch, v.revision, v.time.String(), v.modified,
+	)
+}
+
+// Compare compares the provided version with the current version. Some of the
+// build information is included in the comparison. This means that revision,
+// time and the modified values are compared aswell.
+//
+// Returns 0, -1, or 1 based on the fact if the version is equal, smaller or
+// larger than the incoming version.
+// If one of the versions is modified then a comparison is not possible, since
+// it cannot be said with certainty that the two versions are the same. In this
+// case a -2 is returned.
+func (v *Version) Compare(toCompare *Version) int {
+	if v.modified || toCompare.modified {
+		return -2
+	}
+
+	// compare the major segment.
+	if c := compareVersionSegment(v.major, toCompare.major); c != 0 {
+		return c
+	}
+	// compare the minor segment.
+	if c := compareVersionSegment(v.minor, toCompare.minor); c != 0 {
+		return c
+	}
+	// compare the patch segment.
+	if c := compareVersionSegment(v.patch, toCompare.patch); c != 0 {
+		return c
+	}
+
+	// basic version is the same; now the commit information is compared.
+	if v.revision != toCompare.revision {
+		if v.time.Before(toCompare.time) {
+			return -1
+		}
+		return 1
+	}
+
+	return 0
+}
+
+// compareVersionSegment is a helper function that allows to compare version
+// segments like, e.g. major, minor and patch between two versions.
+//
+// Left describes the segment of the version that should be compared against
+// and right describes the incoming version.
+//
+// The function returns 0, -1, or 1 based on the fact if the version segment
+// to be compared against is equal, smaller or larger than the incoming version
+// segment.
+func compareVersionSegment(left, right int) int {
+	if left < right {
+		return -1
+	}
+
+	if left > right {
+		return 1
+	}
+
+	return 0
+}
+
+// parseVersionString is a helper function that parses the given string into
+// three integers: major, minor and patch. The integers are returned as an
+// array of integers in that respective order.
+func parseVersionString(s string) ([]int, error) {
+	versionSegments := strings.SplitN(s, ".", 3)
+
+	res := make([]int, 3)
+	for i, versionSegment := range versionSegments {
+		versionSegmentAsInt, err := strconv.ParseInt(versionSegment, 10, 64)
+		if err != nil {
+			return nil, err
+		}
+		res[i] = int(versionSegmentAsInt)
+	}
+	return res, nil
+}
diff --git a/controller/version/version_test.go b/controller/version/version_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9ae0dbaa713c0c7129782c425491bbcaab8281aa
--- /dev/null
+++ b/controller/version/version_test.go
@@ -0,0 +1,468 @@
+package version
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func Test_version_NewVersion(t *testing.T) {
+	// custom options for comparer
+	opts := cmp.Options{
+		cmp.Comparer(func(left, right *Version) bool {
+			if left == nil && right == nil {
+				return true
+			}
+			if left.major == right.major &&
+				left.minor == right.minor &&
+				left.patch == right.patch {
+				return true
+			}
+
+			return false
+		}),
+	}
+
+	type args struct {
+		versionString string
+	}
+
+	tests := []struct {
+		name    string
+		args    args
+		want    *Version
+		wantErr bool
+	}{
+		{
+			name: "default",
+			args: args{},
+			want: &Version{
+				major:            0,
+				minor:            0,
+				patch:            0,
+				BuildInformation: BuildInformation{},
+			},
+			wantErr: false,
+		},
+		{
+			name: "faulty version string",
+			args: args{
+				versionString: "faulty.version.string",
+			},
+			want:    nil,
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// This is not run in parallel since we change the versionString
+			// and this would therefore result in a data race.
+			//
+			// versionString is and should only be touched through ldflags with
+			// `go build`.
+			if tt.name != "default" {
+				versionString = tt.args.versionString
+			}
+
+			got, err := NewVersion()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("NewVersion() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+
+			if !cmp.Equal(got, tt.want, opts...) {
+				t.Errorf("NewVersion() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_version_Compare(t *testing.T) {
+	timeNormal, err := time.Parse(time.RFC3339, "2022-08-19T11:11:02+00:00")
+	if err != nil {
+		t.Errorf("Compare() error = %v", err)
+	}
+
+	timeBefore, err := time.Parse(time.RFC3339, "2022-08-18T11:11:02+00:00")
+	if err != nil {
+		t.Errorf("Compare() error = %v", err)
+	}
+
+	timeAfter, err := time.Parse(time.RFC3339, "2022-08-22T11:11:02+00:00")
+	if err != nil {
+		t.Errorf("Compare() error = %v", err)
+	}
+
+	v := &Version{
+		major: 1,
+		minor: 3,
+		patch: 101,
+		BuildInformation: BuildInformation{
+			goos:     "linux",
+			goarch:   "amd64",
+			revision: "34979478a8c56269dd59419d04d9080c1103174d",
+			time:     timeNormal,
+			modified: false,
+		},
+	}
+
+	type args struct {
+		inputVersion *Version
+	}
+
+	tests := []struct {
+		name string
+		args args
+		want int
+	}{
+		{
+			name: "input version is equal",
+			args: args{
+				inputVersion: &Version{
+					major: 1,
+					minor: 3,
+					patch: 101,
+					BuildInformation: BuildInformation{
+						goos:     "linux",
+						goarch:   "amd64",
+						revision: "34979478a8c56269dd59419d04d9080c1103174d",
+						time:     timeNormal,
+						modified: false,
+					},
+				},
+			},
+			want: 0,
+		},
+		{
+			name: "input version is smaller based on major",
+			args: args{
+				inputVersion: &Version{
+					major: 0,
+					minor: 0,
+					patch: 101,
+					BuildInformation: BuildInformation{
+						goos:     "linux",
+						goarch:   "amd64",
+						revision: "34979478a8c56269dd59419d04d9080c1103174d",
+						time:     timeNormal,
+						modified: false,
+					},
+				},
+			},
+			want: 1,
+		},
+		{
+			name: "input version is smaller based on minor",
+			args: args{
+				inputVersion: &Version{
+					major: 1,
+					minor: 0,
+					patch: 101,
+					BuildInformation: BuildInformation{
+						goos:     "linux",
+						goarch:   "amd64",
+						revision: "34979478a8c56269dd59419d04d9080c1103174d",
+						time:     timeNormal,
+						modified: false,
+					},
+				},
+			},
+			want: 1,
+		},
+		{
+			name: "input version is smaller based on patch",
+			args: args{
+				inputVersion: &Version{
+					major: 1,
+					minor: 3,
+					patch: 78,
+					BuildInformation: BuildInformation{
+						goos:     "linux",
+						goarch:   "amd64",
+						revision: "34979478a8c56269dd59419d04d9080c1103174d",
+						time:     timeNormal,
+						modified: false,
+					},
+				},
+			},
+			want: 1,
+		},
+		{
+			name: "input version is smaller based on commit",
+			args: args{
+				inputVersion: &Version{
+					major: 1,
+					minor: 3,
+					patch: 101,
+					BuildInformation: BuildInformation{
+						goos:     "linux",
+						goarch:   "amd64",
+						revision: "14379478a8c56269dd59419d04c9080c1103174d",
+						time:     timeBefore,
+						modified: false,
+					},
+				},
+			},
+			want: 1,
+		},
+		{
+			name: "input version is larger based on major",
+			args: args{
+				inputVersion: &Version{
+					major: 2,
+					minor: 3,
+					patch: 78,
+					BuildInformation: BuildInformation{
+						goos:     "linux",
+						goarch:   "amd64",
+						revision: "34979478a8c56269dd59419d04d9080c1103174d",
+						time:     timeNormal,
+						modified: false,
+					},
+				},
+			},
+			want: -1,
+		},
+		{
+			name: "input version is larger based on minor",
+			args: args{
+				inputVersion: &Version{
+					major: 1,
+					minor: 4,
+					patch: 78,
+					BuildInformation: BuildInformation{
+						goos:     "linux",
+						goarch:   "amd64",
+						revision: "34979478a8c56269dd59419d04d9080c1103174d",
+						time:     timeNormal,
+						modified: false,
+					},
+				},
+			},
+			want: -1,
+		},
+		{
+			name: "input version is larger based on patch",
+			args: args{
+				inputVersion: &Version{
+					major: 1,
+					minor: 3,
+					patch: 103,
+					BuildInformation: BuildInformation{
+						goos:     "linux",
+						goarch:   "amd64",
+						revision: "34979478a8c56269dd59419d04d9080c1103174d",
+						time:     timeNormal,
+						modified: false,
+					},
+				},
+			},
+			want: -1,
+		},
+		{
+			name: "input version is larger based on commit",
+			args: args{
+				inputVersion: &Version{
+					major: 1,
+					minor: 3,
+					patch: 101,
+					BuildInformation: BuildInformation{
+						goos:     "linux",
+						goarch:   "amd64",
+						revision: "14379478a8c56269dd59419d04c9080c1103174d",
+						time:     timeAfter,
+						modified: false,
+					},
+				},
+			},
+			want: -1,
+		},
+		{
+			name: "one of the versions is modified",
+			args: args{
+				inputVersion: &Version{
+					BuildInformation: BuildInformation{
+						modified: true,
+					},
+				},
+			},
+			want: -2,
+		},
+	}
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			got := v.Compare(tt.args.inputVersion)
+
+			if !cmp.Equal(got, tt.want) {
+				t.Errorf("Compare() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_version_String(t *testing.T) {
+	timeNow := time.Now()
+	v := &Version{
+		major: 1,
+		minor: 3,
+		patch: 101,
+		BuildInformation: BuildInformation{
+			goos:     "linux",
+			goarch:   "amd64",
+			revision: "34979478a8c56269dd59419d04d9080c1103174d",
+			time:     timeNow,
+			modified: false,
+		},
+	}
+
+	tests := []struct {
+		name string
+		want string
+	}{
+		{
+			name: "default",
+			want: fmt.Sprintf(`Version: 1.3.101
+Build Information:
+    - GOOS: linux
+    - GOARCH: amd64
+    - Revision: 34979478a8c56269dd59419d04d9080c1103174d
+    - Time: %s
+    - Modified (dirty): false`, timeNow.String()),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := v.String()
+
+			if !cmp.Equal(got, tt.want) {
+				t.Errorf("String() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_version_compareVersionSegment(t *testing.T) {
+	type args struct {
+		left, right int
+	}
+
+	tests := []struct {
+		name string
+		args args
+		want int
+	}{
+		{
+			name: "left is smaller && right is larger",
+			args: args{
+				left:  5,
+				right: 8,
+			},
+			want: -1,
+		},
+		{
+			name: "left is larger && right is smaller",
+			args: args{
+				left:  9,
+				right: 3,
+			},
+			want: 1,
+		},
+		{
+			name: "left and right are equal",
+			args: args{
+				left:  4,
+				right: 4,
+			},
+			want: 0,
+		},
+	}
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			got := compareVersionSegment(tt.args.left, tt.args.right)
+
+			if !cmp.Equal(got, tt.want) {
+				t.Errorf("compareVersionSegment() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_version_parseVersionString(t *testing.T) {
+	type args struct {
+		input string
+	}
+
+	tests := []struct {
+		name    string
+		args    args
+		want    []int
+		wantErr bool
+	}{
+		{
+			name: "default",
+			args: args{
+				input: "1.0.4",
+			},
+			want:    []int{1, 0, 4},
+			wantErr: false,
+		},
+		{
+			name: "too many values in version string",
+			args: args{
+				input: "1.0.4.259",
+			},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name: "faulty version string - no numbers",
+			args: args{
+				input: "this is wrong",
+			},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name: "faulty version string - wrong delimiter",
+			args: args{
+				input: "1-2-187",
+			},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name: "faulty version string - mixed",
+			args: args{
+				input: "1.abc.283",
+			},
+			want:    nil,
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			got, err := parseVersionString(tt.args.input)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("parseVersionString() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+
+			if !cmp.Equal(got, tt.want) {
+				t.Errorf("parseVersionString() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}