diff --git a/.dockerignore b/.dockerignore
deleted file mode 100644
index 381df275579468051eb8b56a7337b0cfc91c67ea..0000000000000000000000000000000000000000
--- a/.dockerignore
+++ /dev/null
@@ -1,4 +0,0 @@
-**
-!piratar
-!piratar.conf
-!default.jpg
diff --git a/.env b/.env
new file mode 100644
index 0000000000000000000000000000000000000000..c012b30ae95c36a3691cdc678e10fad18d2eedb1
--- /dev/null
+++ b/.env
@@ -0,0 +1,2 @@
+export OCTOPUS=https://chobotnice.pirati.cz/graphql/
+export OCTPUS_DEBUG=1
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5ac96414271d44a899abca06475a421a2b6c51dd..10d37664c6fd300019c224909b52a404bd75f6a8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,11 +1,10 @@
-image: docker:20.10.9
+image: docker:latest
 
 variables:
   DOCKER_TLS_CERTDIR: "/certs"
-  IMAGE_VER: 2.1.0
 
 services:
-  - docker:20.10.9-dind
+  - docker:dind
 
 before_script:
   - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
@@ -13,7 +12,8 @@ before_script:
 build:
   stage: build
   script:
+    - VERSION=`cat VERSION`
     - docker pull $CI_REGISTRY_IMAGE:latest || true
-    - docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$IMAGE_VER --tag $CI_REGISTRY_IMAGE:latest .
-    - docker push $CI_REGISTRY_IMAGE:$IMAGE_VER
+    - docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$VERSION --tag $CI_REGISTRY_IMAGE:latest .
+    - docker push $CI_REGISTRY_IMAGE:$VERSION
     - docker push $CI_REGISTRY_IMAGE:latest
diff --git a/Dockerfile b/Dockerfile
index 3f4c2f30fc059346737f4bd547fce0f37091fd34..0de64e5c506848d1ec05d290d9fa1507647b8e40 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,10 +1,10 @@
-FROM alpine:latest
-RUN apk update && apk add perl-mojolicious perl-io-socket-ssl perl-app-cpanminus make
-RUN cpanm GraphQL::Client
+FROM golang:alpine AS build-stage
+ADD . ./
+RUN CGO_ENABLED=0 GOOS=linux go build -o /piratar
 
-ADD . /opt/piratar
-WORKDIR /opt/piratar
-
-USER nobody
+FROM alpine
+WORKDIR /app
+COPY --from=build-stage /piratar /app
+COPY default.jpg /app
 EXPOSE 3000
-CMD /opt/piratar/piratar daemon -c 3000
+ENTRYPOINT ["/app/piratar"]
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000000000000000000000000000000000000..4a36342fcab700951adb18ae7adc930997f6c3f4
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+3.0.0
diff --git a/compose.yaml b/compose.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6e0f8b7ea5d93d941043d5d0676c2c88489d0569
--- /dev/null
+++ b/compose.yaml
@@ -0,0 +1,7 @@
+services:
+  app:
+    image: piratar
+    environment:
+        OCTOPUS: https://chobotnice.pirati.cz/graphql/
+    ports:
+      - "3000:3000"
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..68b7c45ecfb1546695c2a0c1bb26685bfb501d81
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,14 @@
+module pirates/piratar
+
+go 1.22.2
+
+require pirates/piratar/octopus v0.0.0-00010101000000-000000000000
+
+require (
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/machinebox/graphql v0.2.2 // indirect
+	github.com/matryer/is v1.4.1 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+)
+
+replace pirates/piratar/octopus => ./octopus
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..2fe3d62bf9a67c7de92a54c8dfd3a9ce30f6e1ed
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,8 @@
+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/machinebox/graphql v0.2.2 h1:dWKpJligYKhYKO5A2gvNhkJdQMNZeChZYyBbrZkBZfo=
+github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA=
+github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
+github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..8635994e0016fc991dd316899a101efbbd4f29cf
--- /dev/null
+++ b/main.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+    "fmt"
+    "log"
+    "path"
+    "strings"
+    "net/http"
+    "pirates/piratar/octopus"
+)
+
+func main() {
+	http.HandleFunc("/", handler)
+
+	port := 3000 // TODO: ENV
+	fmt.Printf("Piratar service listening on port %d...\n", port)
+
+	if err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); err != nil {
+		log.Fatalf("Server failed: %v", err)
+	}
+}
+
+func handler(w http.ResponseWriter, r *http.Request) {
+
+    // Extrakce username nebo UUID
+    id := strings.ToLower(path.Base(r.URL.Path))
+    if dotIndex := strings.LastIndex(id, "."); dotIndex != -1 {
+		id = id[:dotIndex]
+	}
+
+    // Profilova fotka nebo default
+    if photo, err := octopus.ProfilePhoto(id); err == nil {
+	    fmt.Printf("Piratar for %s: %s\n", id, photo)
+	    http.Redirect(w, r, photo, http.StatusFound)
+    } else {
+	    fmt.Printf("Piratar for %s not found\n", id)
+   	    w.Header().Set("Content-Type", "image/jpeg")
+        http.ServeFile(w, r, "default.jpg")
+    }
+}
diff --git a/octopus/go.mod b/octopus/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..17405e123116173147946b8cfb9cf050fffc1016
--- /dev/null
+++ b/octopus/go.mod
@@ -0,0 +1,3 @@
+module pirates/piratar/octopus
+
+go 1.22.2
diff --git a/octopus/octopus.go b/octopus/octopus.go
new file mode 100644
index 0000000000000000000000000000000000000000..f7ef86055f0c26c7c4e7b5eb8821c196be643561
--- /dev/null
+++ b/octopus/octopus.go
@@ -0,0 +1,60 @@
+package octopus
+
+import (
+    "os"
+    "fmt"
+    "log"
+    "errors"
+    "context"
+    "github.com/google/uuid"
+    "github.com/machinebox/graphql"
+)
+
+func ProfilePhoto (id string) (string, error) {
+    var keyname string
+    var response struct {
+       AllPeople struct {
+           Edges []struct {
+               Node struct {
+                   ProfilePhoto string `json:"profilePhoto"`
+               }                       `json:"node"`
+           }                           `json:"edges"`
+       }                               `json:"allPeople"`
+    }
+
+    if _, err := uuid.Parse(id); err == nil {
+        keyname = "keycloakId"
+    } else {
+        keyname = "username"
+    }
+
+    // GraphAPI klient
+    octopus := graphql.NewClient(os.Getenv("OCTOPUS"))
+    if os.Getenv("DEBUG") != "" {
+        octopus.Log = func(s string) { log.Println(s) }
+    }
+
+    // Request
+    req := graphql.NewRequest(fmt.Sprintf(
+    `query profilePhoto($username: String!) {
+        allPeople(filters: {%s: {iExact: $username}}) {
+            edges { node { profilePhoto } }
+        }}
+    `, keyname ))
+
+    req.Var("username", id)
+
+// define a Context for the request
+    ctx := context.Background()
+
+// run it and capture the response
+    if err := octopus.Run(ctx, req, &response); err != nil {
+        log.Fatal(err)
+    }
+
+    if people := response.AllPeople.Edges; len(people) == 1 {
+        return people[0].Node.ProfilePhoto, nil
+    } else {
+        return "", errors.New("User not found")
+    }
+}
diff --git a/piratar b/piratar
deleted file mode 100755
index e1d838b5edfcc0ff5e0e8715d81fddec65e31488..0000000000000000000000000000000000000000
--- a/piratar
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env perl
-use Mojolicious::Lite -signatures;
-use Mojo::UserAgent;
-use Mojolicious::Static;
-use GraphQL::Client;
-use Mojo::Util qw(dumper);
-
-get '/*url' => sub ($c) {
-
-    my $username = lc($c->param('url'));
-    $username =~ s/^.*\///;
-    $username =~ s/\.(png|jpg|gif)$//;
-    $c->app->log->debug("Username: $username");
-
-    my $gq = GraphQL::Client->new(url => $ENV{OCTOPUS});
-    my $rc = $gq->execute(qq[ query MyQuery {
-        allPeople(filters: {username: {iExact: "$username"}}) {
-            edges { node { profilePhoto } }
-        }}],
-    );
-
-    if ($rc->{errors}) {
-        $c->app->log->warn("Octopus error: " . dumper $rc->{errors});
-    }
-    elsif ( $rc->{data} ) {
-        $c->app->log->debug("Octopus response: " . dumper $rc->{data});
-
-        my $user  = $rc->{data}{allPeople}{edges}[0]{node};
-
-        if ( my $photo = $user->{profilePhoto}) {
-            $c->app->log->info("Avatar for user $username: $photo");
-            $c->redirect_to($photo);
-            return;
-        }
-    }
-
-    $c->app->log->info("Avatar for user $username: FALLBACK");
-    $c->reply->file('default.jpg');
-};
-
-app->start;