diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ced011b547574b51a806c680277ec09a0a2ba9a4
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,21 @@
+stages:
+  - build
+
+build-images:
+  services:
+    - docker:19.03.12-dind
+  tags:
+    - api
+  stage: build
+  image: registry.e-biz.fr/apuret/deadlock-public/ci-deadlock-cli:1.2.4
+  script:
+    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+    - ./build/build-image.sh $CI_REGISTRY_IMAGE
+  cache:
+    paths:
+      - resources/*/runner
+      - resources/*/.hash
+  only:
+    - master
+  tags:
+    - docker
diff --git a/README.md b/README.md
index 0f91ff98d875729d63318d193459e44c814480eb..69a1ee566d3491d91e21ed7b4fb1eb089cd0fce5 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,6 @@
-# Deadlock challenges example
+# Example of a private Deadlock challenges repository
+
+Feel free to inspire take it as a template and add your challenges.
+
+You can see how to create a Deadlock challenge [here](https://deadlock-resources.github.io/challenge-documentation/).
 
-An example custom Deadlock custom challenges repository.
\ No newline at end of file
diff --git a/build/build-image.sh b/build/build-image.sh
new file mode 100755
index 0000000000000000000000000000000000000000..2d37e84d3bb5e33a762410a5d7d5132fd2521322
--- /dev/null
+++ b/build/build-image.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+# include parse_yaml function
+. ./build/parse_yaml.sh
+
+# Verify that docker daemon is up
+until docker ps > /dev/null; do
+    echo  "Waiting for docker daemon to wake up..."
+    sleep 2
+done
+
+# $1 gitlab registry name
+
+for d in resources/*/; do
+    # read yaml file
+    eval $(parse_yaml "$d/challenge.yaml" "mission_");
+    IMAGE_NAME="$mission_name:$mission_version";
+    echo "=============================================================================";
+    echo "> Checking if image $IMAGE_NAME exists in registry <";
+    # Careful experimental feature (docker manifest inspect), can be change in the future.
+    docker manifest inspect "$1/$IMAGE_NAME" > /dev/null && echo "> Found.. continue" && continue || true;
+
+    # go into the folder to keep relative path when executing script
+    cd $d
+
+    [ -f build.sh ] && echo "> Building challenge <" && ./build.sh
+    [ -f entry.rs ] && echo "> Building runner for metamorph challenge <" && dcli build .
+    echo "> Building Docker image $IMAGE_NAME <";
+    docker build . -q -t "$1/$IMAGE_NAME" && docker push "$1/$IMAGE_NAME" || exit 1
+    if [ -d "./services" ]; then
+        for s in ./services/*/; do
+            echo ">>> Checking service image $s <";
+            eval $(parse_yaml "./service.yaml" "service_");
+            IMAGE_SERVICE_NAME="$mission_name/$service_name:$mission_version";
+            docker manifest inspect "$1/$IMAGE_SERVICE_NAME" > /dev/null && echo "> Found.. continue" && continue || true;
+            echo ">>> Building service image $s <";
+            docker build $s -q -t "$1/$IMAGE_SERVICE_NAME";
+            docker push "$1/$IMAGE_SERVICE_NAME";
+        done
+    fi
+
+    # back to previous folder
+    cd -
+done
+
diff --git a/build/parse_yaml.sh b/build/parse_yaml.sh
new file mode 100644
index 0000000000000000000000000000000000000000..ca35ceb3c72560bb985f283f8cd8cdedae6f7f3c
--- /dev/null
+++ b/build/parse_yaml.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+parse_yaml() {
+   local prefix=$2
+   local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
+   sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
+        -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p"  $1 |
+   awk -F$fs '{
+      indent = length($1)/2;
+      vname[indent] = $2;
+      for (i in vname) {if (i > indent) {delete vname[i]}}
+      if (length($3) > 0) {
+         vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
+         printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
+      }
+   }'
+}
\ No newline at end of file
diff --git a/resources/code_palindrome_test/Dockerfile b/resources/code_palindrome_test/Dockerfile
new file mode 100755
index 0000000000000000000000000000000000000000..df536faae94b22e93ac2dd1bc55e20de83e7fb8a
--- /dev/null
+++ b/resources/code_palindrome_test/Dockerfile
@@ -0,0 +1,6 @@
+FROM openjdk:8-jdk-slim
+WORKDIR /
+COPY src src
+COPY run.sh /
+RUN chmod +x run.sh
+ENTRYPOINT ["/run.sh"]
diff --git a/resources/code_palindrome_test/challenge.yaml b/resources/code_palindrome_test/challenge.yaml
new file mode 100755
index 0000000000000000000000000000000000000000..325b21501a5bb407c87abd9713b77a3b942c96f0
--- /dev/null
+++ b/resources/code_palindrome_test/challenge.yaml
@@ -0,0 +1,14 @@
+version: 1.0
+name: code_palindrome_test
+label: Example exercice - palindrome
+description: Can you read it from whatever side
+level: jarjarbinks
+type: CODING
+xp:
+  java: 1
+  programming: 1
+coding:
+  templateDirectory: src/main/java/template
+  successDirectory: src/main/java/success
+  target: Palindrome.java
+  editorMode: java
diff --git a/resources/code_palindrome_test/docs/briefing.md b/resources/code_palindrome_test/docs/briefing.md
new file mode 100755
index 0000000000000000000000000000000000000000..a8a6eae9e0145141c7f11c61f4b50cee222497e9
--- /dev/null
+++ b/resources/code_palindrome_test/docs/briefing.md
@@ -0,0 +1,7 @@
+# Palindrome
+
+A Palindrome is a word that can be read indifferently from left or right or from right to left and keeps the same meaning.
+
+Exemple: eye
+
+You have to check if a given word is a palindrome or not.
\ No newline at end of file
diff --git a/resources/code_palindrome_test/docs/fr/briefing.md b/resources/code_palindrome_test/docs/fr/briefing.md
new file mode 100644
index 0000000000000000000000000000000000000000..84a29ebb07c8338210e04585d6415647b1649aeb
--- /dev/null
+++ b/resources/code_palindrome_test/docs/fr/briefing.md
@@ -0,0 +1,7 @@
+# Palindrome
+
+Un palindrome est un mot qui peut être lu indifféremment de gauche à droite ou de droite à gauche et qui conserve le même sens.
+
+Exemple: elle
+
+Tu dois vérifier pour un mot donné s'il s'agit d'un palindrome ou non.
\ No newline at end of file
diff --git a/resources/code_palindrome_test/docs/fr/hint1.md b/resources/code_palindrome_test/docs/fr/hint1.md
new file mode 100644
index 0000000000000000000000000000000000000000..d99e48ea7145f8dacc54becc45c0bfc2979cd075
--- /dev/null
+++ b/resources/code_palindrome_test/docs/fr/hint1.md
@@ -0,0 +1,3 @@
+# Indice 1
+
+Regarde du côté du StringBuilder.
diff --git a/resources/code_palindrome_test/docs/fr/hint2.md b/resources/code_palindrome_test/docs/fr/hint2.md
new file mode 100644
index 0000000000000000000000000000000000000000..8fa0a36dd7d0fc23bbdeaaaa027a1cc7321047be
--- /dev/null
+++ b/resources/code_palindrome_test/docs/fr/hint2.md
@@ -0,0 +1,3 @@
+# Indice 2
+
+Tu peux aussi t'aider de l'API Stream pour les String.
\ No newline at end of file
diff --git a/resources/code_palindrome_test/docs/hint1.md b/resources/code_palindrome_test/docs/hint1.md
new file mode 100755
index 0000000000000000000000000000000000000000..aa044e6b19668d0efcf90bd33bcf8374240e50ed
--- /dev/null
+++ b/resources/code_palindrome_test/docs/hint1.md
@@ -0,0 +1,3 @@
+# Hint 1
+
+Look into the functions of StringBuilder.
diff --git a/resources/code_palindrome_test/docs/hint2.md b/resources/code_palindrome_test/docs/hint2.md
new file mode 100644
index 0000000000000000000000000000000000000000..5f3ebc3e87bf4aa63958ff49f2e035a904ab70df
--- /dev/null
+++ b/resources/code_palindrome_test/docs/hint2.md
@@ -0,0 +1,3 @@
+# Hint 2
+
+You can check the stream API for Strings.
\ No newline at end of file
diff --git a/resources/code_palindrome_test/run.sh b/resources/code_palindrome_test/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..6bf43063881d86e8b56674a69af8de6d39c7d2a0
--- /dev/null
+++ b/resources/code_palindrome_test/run.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -e
+echo "Compiling java code"
+cd src/main/java
+javac -Xmaxerrs 1 app/*.java
+echo "Executing code"
+java -Xms32m -Xmx64m app/$1
+echo "Done"
diff --git a/resources/code_palindrome_test/src/main/java/app/Logger.java b/resources/code_palindrome_test/src/main/java/app/Logger.java
new file mode 100755
index 0000000000000000000000000000000000000000..f4ed299daff4c7332b3d74db9152813979b751e0
--- /dev/null
+++ b/resources/code_palindrome_test/src/main/java/app/Logger.java
@@ -0,0 +1,38 @@
+package app;
+
+public class Logger {
+
+    public static void log(String message) {
+        System.out.println("----------------------------------------------------");
+        System.out.println(message);
+        System.out.println("----------------------------------------------------");
+    }
+
+    public static void logSuccess() {
+        System.out.println("----------------------------------------------------");
+        System.out.println("Good job!");
+        System.out.println("----------------------------------------------------");
+    }
+
+    public static void logNoMatch(Object... args) {
+        System.err.println("--------------------------------------------------------------------------");
+        System.err.println(String.format("You have to try again, the word %s evaluated to %s, expecting %s", args[0],
+                args[1], args[2]));
+        System.err.println("--------------------------------------------------------------------------");
+    }
+
+    public static void logException(Object... args) {
+        System.err.println("----------------------------------------------------");
+        System.err.println("An error occurred during runtime.");
+        System.err.println("Details:" + args[0]);
+    }
+
+    public static void logException(Throwable throwable) {
+        System.err.println("----------------------------------------------------");
+        System.err.println("Something bad happened!");
+        System.err.println(throwable.getMessage());
+        throwable.printStackTrace();
+        System.err.println("----------------------------------------------------");
+    }
+
+}
diff --git a/resources/code_palindrome_test/src/main/java/app/Run.java b/resources/code_palindrome_test/src/main/java/app/Run.java
new file mode 100755
index 0000000000000000000000000000000000000000..ff2ff8b3bef807735e4e877ed26543bd9c79cac7
--- /dev/null
+++ b/resources/code_palindrome_test/src/main/java/app/Run.java
@@ -0,0 +1,17 @@
+package app;
+
+import static template.Palindrome.isPalindrome;;
+
+public class Run {
+
+    public static void main(String[] args) {
+        String[] words = { "pop", "non", "eye", "hello" };
+        try {
+            for (String n : words) {
+                Logger.log("Input : " + n + " - Output : " + isPalindrome(n));
+            }
+        } catch (RuntimeException e) {
+            Logger.logException(e);
+        }
+    }
+}
diff --git a/resources/code_palindrome_test/src/main/java/app/Solve.java b/resources/code_palindrome_test/src/main/java/app/Solve.java
new file mode 100755
index 0000000000000000000000000000000000000000..c2dfd9e5735eb20694c533daf3b9b83d3160c3cd
--- /dev/null
+++ b/resources/code_palindrome_test/src/main/java/app/Solve.java
@@ -0,0 +1,31 @@
+package app;
+
+import static template.Palindrome.isPalindrome;
+import static success.Palindrome.isPalindrome;
+
+import java.lang.Math;
+import java.util.*;
+
+public class Solve {
+
+    public static void main(String[] args) {
+        List<String> words = Arrays.asList("level", "deadlock", "java", "madam", "check", "rotator", "kayak", "anna");
+        Collections.shuffle(words);
+
+        try {
+            for (String word : words) {
+                boolean userValue = template.Palindrome.isPalindrome(word);
+                boolean expectedValue = success.Palindrome.isPalindrome(word);
+                if (expectedValue != userValue) {
+                    Logger.logNoMatch(word, userValue, expectedValue);
+                    System.exit(1);
+                }
+            }
+            // all test passed successfully
+            Logger.logSuccess();
+        } catch (RuntimeException e) {
+            Logger.logException(e);
+            System.exit(1);
+        }
+    }
+}
diff --git a/resources/code_palindrome_test/src/main/java/success/Palindrome.java b/resources/code_palindrome_test/src/main/java/success/Palindrome.java
new file mode 100644
index 0000000000000000000000000000000000000000..630767b4d6a77d74c5f220c9b5e425aca2dcb22c
--- /dev/null
+++ b/resources/code_palindrome_test/src/main/java/success/Palindrome.java
@@ -0,0 +1,10 @@
+package success;
+
+import java.util.*;
+
+public class Palindrome {
+
+    public static boolean isPalindrome(String word) {
+        return word.equals(new StringBuilder(word).reverse().toString());
+    }
+}
diff --git a/resources/code_palindrome_test/src/main/java/template/Palindrome.java b/resources/code_palindrome_test/src/main/java/template/Palindrome.java
new file mode 100644
index 0000000000000000000000000000000000000000..da3994375628c090e5c316eb9574093797022ff8
--- /dev/null
+++ b/resources/code_palindrome_test/src/main/java/template/Palindrome.java
@@ -0,0 +1,10 @@
+package template;
+
+import java.util.*;
+
+public class Palindrome {
+
+    public static boolean isPalindrome(String word) {
+        return false;
+    }
+}
diff --git a/resources/code_palindrome_test/thumbnail.png b/resources/code_palindrome_test/thumbnail.png
new file mode 100755
index 0000000000000000000000000000000000000000..30f95b3a5e7b468f3dcc0638c29899c9493c4b85
Binary files /dev/null and b/resources/code_palindrome_test/thumbnail.png differ