diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..565a2005c8711d59ae247e836a0f8958128b5484
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,8 @@
+include: "AssignmentChecker/.gitlab-ci/**.yml"
+
+lint_pass:
+    only:
+        variables:
+            - $FOO == "bar"
+    script:
+        - echo
\ No newline at end of file
diff --git a/AssignmentChecker/.gitlab-ci/00_vars.yml b/AssignmentChecker/.gitlab-ci/00_vars.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0af94a753b6ffab5f8892ded39d9b10a92f8e05c
--- /dev/null
+++ b/AssignmentChecker/.gitlab-ci/00_vars.yml
@@ -0,0 +1,22 @@
+variables:
+    GIT_SUBMODULE_STRATEGY: recursive
+    GIT_SUBMODULE_FORCE_HTTPS: "true"
+
+    DOCKER_HOST: tcp://docker:2375
+    DOCKER_TLS_CERTDIR:
+    DOCKER_DRIVER: overlay2
+
+    PROJECT_NAME: DojoAssignmentChecker
+
+    VERSION_DEV_SUFFIX: '-dev'
+
+    GITLAB_API_PROJECT_URL: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}
+
+    DOCKERFILE: Dockerfile_AssignmentChecker
+    DOCKER_PLATFORMS: linux/amd64,linux/arm64/v8
+
+    PROJECT_FOLDER: AssignmentChecker
+
+    PACKAGE_REGISTRY_URL: "${GITLAB_API_PROJECT_URL}/packages/generic/${PROJECT_NAME}"
+
+    WIKI_FOLDER: Wiki
\ No newline at end of file
diff --git a/AssignmentChecker/.gitlab-ci/01_functions.yml b/AssignmentChecker/.gitlab-ci/01_functions.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e77ccd8adc262354fa3941e9db0b2ad25228e9e9
--- /dev/null
+++ b/AssignmentChecker/.gitlab-ci/01_functions.yml
@@ -0,0 +1,81 @@
+.get_version:
+    script:
+        - IS_DEV=$([[ $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH ]] && echo false || echo true)
+        - VERSION=$(jq -r .version $PROJECT_FOLDER/package.json)$([[ $IS_DEV == true ]] && echo $VERSION_DEV_SUFFIX || echo '')
+
+
+.get_packages_url:
+    script:
+        # Wiki
+        - WIKI_ARCHIVE_NAME="${PROJECT_NAME}_Wiki_${VERSION}.tar.xz"
+        - PACKAGE_URL_WIKI="${PACKAGE_REGISTRY_URL}_Wiki/${VERSION}/${WIKI_ARCHIVE_NAME}"
+
+
+.init_dind_script:
+    script:
+        # Install dependencies
+        - apk update
+        - apk add git
+        - apk add jq
+        - apk add curl
+
+        # Init docker buildx
+        - docker login -u $DOCKER_REGISTRY_USER -p $DOCKER_REGISTRY_PASSWORD $DOCKER_REGISTRY
+        - docker buildx create --use
+
+        # Get version from package.json
+        - !reference [ .get_version, script ]
+        - CONTAINER_IMAGE=$DOCKER_REGISTRY_IMAGE:$VERSION
+
+
+.build_script:
+    script:
+        - !reference [ .init_dind_script, script ]
+        - mkdir -p $ARTIFACTS_FOLDER
+
+        # Decrypt env vars for production
+        - apk add npm sed
+        - cd $PROJECT_FOLDER
+        - sed -i -r "s/\{\{VERSION\}\}/${VERSION}/g" src/app.ts
+        - |
+            if [ $CI_COMMIT_REF_PROTECTED == "true" ]; then
+                echo "Decrypt production env vars"
+                sed -i -r "s/(DOTENV_KEY[ ]*:[ ]*[\'\"\`])[^'\"\`]*([\'\"\`])([ ]*\,)?//g" src/app.ts
+                sed -i -r "s/,[\ \n]*\}/\}/g" src/app.ts
+                npx dotenv-vault local decrypt "${DOTENV_PROD_KEY}" > .env
+            
+            fi
+        - cd ..
+
+        # Need to build for each platform separately because of multi-stage builds (docker buildx don't use cache same way as docker build)
+        - >
+            platform_array=$(echo $DOCKER_PLATFORMS | tr "," "\n");
+            for platform in $platform_array; do
+              echo "Buildind for : $platform"
+              docker buildx build --pull --platform $platform --file $DOCKERFILE --tag $CONTAINER_IMAGE .
+            done
+
+
+.clean_release:
+    script:
+        # Delete release if it already exists
+        - 'curl --request DELETE --header "JOB-TOKEN: $CI_JOB_TOKEN" "${GITLAB_API_PROJECT_URL}/releases/${VERSION}"'
+
+        # Delete tag if it already exists (use private-token because job-token don't have permission to delete tags)
+        - 'curl --request DELETE --header "PRIVATE-TOKEN: $GITLAB_PROJECT_ACCESS_TOKEN" "${GITLAB_API_PROJECT_URL}/repository/tags/${VERSION}"'
+
+
+.clean_packages:
+    script:
+        # Get all packages of the project
+        - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" "${GITLAB_API_PROJECT_URL}/packages" > gitlabPackages.json'
+
+        # Filter and select packages to delete (based on version)
+        - packagesToDelete=`jq -r '.[] | select(.version=="'${VERSION}'") | ._links.delete_api_path' gitlabPackages.json`
+
+        # Delete packages by calling Gitlab API
+        - >
+            for deletePath in $packagesToDelete; do
+              echo "Deleting package at path : ${deletePath}"
+              curl --request DELETE --header "JOB-TOKEN: $CI_JOB_TOKEN" "${deletePath}"
+            done
\ No newline at end of file
diff --git a/AssignmentChecker/.gitlab-ci/03_stages.yml b/AssignmentChecker/.gitlab-ci/03_stages.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1b20f1ed1fa05c19ee6d8145ccd33bcc39c85d9d
--- /dev/null
+++ b/AssignmentChecker/.gitlab-ci/03_stages.yml
@@ -0,0 +1,6 @@
+stages:
+    - test
+    - build
+    - clean
+    - upload
+    - release
\ No newline at end of file
diff --git a/AssignmentChecker/.gitlab-ci/04_stageTest.yml b/AssignmentChecker/.gitlab-ci/04_stageTest.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a1b564295624124e216ae7bba18ac6950590eccb
--- /dev/null
+++ b/AssignmentChecker/.gitlab-ci/04_stageTest.yml
@@ -0,0 +1,12 @@
+test:build:
+    stage: test
+    tags:
+        - build
+        - dind
+    image: docker:latest
+    services:
+        - docker:dind
+    script:
+        - !reference [ .build_script, script ]
+    rules:
+        -   if: '$CI_COMMIT_TAG =~ "/^$/" && $CI_COMMIT_REF_PROTECTED != "true"'
\ No newline at end of file
diff --git a/AssignmentChecker/.gitlab-ci/05_stageBuild.yml b/AssignmentChecker/.gitlab-ci/05_stageBuild.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e2acdd1042c77d84b45e63e3b8b2cdaf217d92bc
--- /dev/null
+++ b/AssignmentChecker/.gitlab-ci/05_stageBuild.yml
@@ -0,0 +1,15 @@
+build:version:
+    stage: build
+    tags:
+        - build
+        - dind
+    image: docker:latest
+    services:
+        - docker:dind
+    script:
+        - !reference [ .build_script, script ]
+
+        # Here docker buildx can use cached images created in previous step
+        - docker buildx build --platform $DOCKER_PLATFORMS --file $DOCKERFILE --push --tag $CONTAINER_IMAGE .
+    rules:
+        -   if: '$CI_COMMIT_REF_PROTECTED == "true"'
\ No newline at end of file
diff --git a/AssignmentChecker/.gitlab-ci/06_stageClean.yml b/AssignmentChecker/.gitlab-ci/06_stageClean.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f8b6dba94adccbefeda89205f59a704b7f6d3875
--- /dev/null
+++ b/AssignmentChecker/.gitlab-ci/06_stageClean.yml
@@ -0,0 +1,75 @@
+clean:release:
+    stage: clean
+    tags:
+        - gitlab_clean
+    image: registry.gitlab.com/gitlab-ci-utils/curl-jq:latest
+    script:
+        - !reference [ .get_version, script ]
+        - !reference [ .clean_release, script ]
+    rules:
+        -   if: '$CI_COMMIT_REF_PROTECTED == "true"'
+
+
+clean:packages:
+    stage: clean
+    tags:
+        - gitlab_clean
+    image: registry.gitlab.com/gitlab-ci-utils/curl-jq:latest
+    script:
+        - !reference [ .get_version, script ]
+        - !reference [ .clean_packages, script ]
+    rules:
+        -   if: '$CI_COMMIT_REF_PROTECTED == "true"'
+
+
+clean:dev:release:
+    stage: clean
+    tags:
+        - gitlab_clean
+    image: registry.gitlab.com/gitlab-ci-utils/curl-jq:latest
+    script:
+        - !reference [ .get_version, script ]
+        - VERSION="${VERSION}${VERSION_DEV_SUFFIX}"
+        - !reference [ .clean_release, script ]
+    rules:
+        -   if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
+
+
+clean:dev:packages:
+    stage: clean
+    tags:
+        - gitlab_clean
+    image: registry.gitlab.com/gitlab-ci-utils/curl-jq:latest
+    script:
+        - !reference [ .get_version, script ]
+        - VERSION="${VERSION}${VERSION_DEV_SUFFIX}"
+        - !reference [ .clean_packages, script ]
+    rules:
+        -   if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
+
+
+clean:dev:dockerhub:
+    stage: clean
+    tags:
+        - clean
+        - dind
+    image: docker:latest
+    services:
+        - docker:dind
+    script:
+        - !reference [ .init_dind_script, script ]
+        - VERSION="${VERSION}${VERSION_DEV_SUFFIX}"
+
+        # Remove dev tag
+        ## Login to dockerhub (get JWT token)
+        - GET_TOKEN_DATA=$(jq --null-input --arg username "$DOCKER_REGISTRY_USER" --arg password "$DOCKER_REGISTRY_PASSWORD" '{"username":$username,"password":$password}')
+        - >
+            TOKEN=`curl --data "${GET_TOKEN_DATA}" \
+              --header "Content-Type: application/json" \
+              --header "JOB-TOKEN: $CI_JOB_TOKEN" \
+              "https://hub.docker.com/v2/users/login/" | jq -r .token`
+
+        ## Delete tag
+        - 'curl --header "Authorization: JWT ${TOKEN}" --request DELETE "https://hub.docker.com/v2/repositories/${DOCKER_REGISTRY_IMAGE}/tags/${VERSION}/"'
+    rules:
+        -   if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
\ No newline at end of file
diff --git a/AssignmentChecker/.gitlab-ci/07_stageUpload.yml b/AssignmentChecker/.gitlab-ci/07_stageUpload.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4c9bd456380ebbbbaa2e0498321c89e76427e16c
--- /dev/null
+++ b/AssignmentChecker/.gitlab-ci/07_stageUpload.yml
@@ -0,0 +1,22 @@
+upload:packages:wiki:
+    stage: upload
+    tags:
+        - gitlab_package
+    image: registry.gitlab.com/gitlab-ci-utils/curl-jq:latest
+    script:
+        # Install dependencies
+        - apk update
+        - apk add xz
+
+        - !reference [ .get_version, script ]
+        - !reference [ .get_packages_url, script ]
+
+        # Create archive
+        - mkdir -p $ARTIFACTS_FOLDER
+        - WIKI_ARCHIVE_PATH="${ARTIFACTS_FOLDER}/${WIKI_ARCHIVE_NAME}"
+        - tar -v -c -C "${CI_PROJECT_DIR}/${WIKI_FOLDER}" -J -f "${WIKI_ARCHIVE_PATH}" . # Ubuntu: tar --verbose --create --cd wiki-test-2 --xz --file file.tar.bz2
+
+        # Send package
+        - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${WIKI_ARCHIVE_PATH} "${PACKAGE_URL_WIKI}";'
+    rules:
+        -   if: '$CI_COMMIT_REF_PROTECTED == "true"'
\ No newline at end of file
diff --git a/AssignmentChecker/.gitlab-ci/08_stageRelease.yml b/AssignmentChecker/.gitlab-ci/08_stageRelease.yml
new file mode 100644
index 0000000000000000000000000000000000000000..325d173cd88c4336e573d1dd33f6ffbe7745c653
--- /dev/null
+++ b/AssignmentChecker/.gitlab-ci/08_stageRelease.yml
@@ -0,0 +1,103 @@
+release:dockerhub:latest:
+    stage: release
+    tags:
+        - release
+        - dind
+    image: docker:latest
+    services:
+        - docker:dind
+    script:
+        - !reference [ .init_dind_script, script ]
+
+        # Push latest tag
+        - docker buildx imagetools create $CONTAINER_IMAGE --tag $DOCKER_REGISTRY_IMAGE:latest
+    rules:
+        -   if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
+
+
+release:wiki:
+    stage: release
+    tags:
+        - release
+    image: alpine:latest
+    script:
+        - apk update
+        - apk add git
+        - apk add jq
+
+        - !reference [ .get_version, script ]
+
+        # Define URL for the wiki in terms of project-agnostic predefined variables
+        - WIKI_URL="${CI_SERVER_PROTOCOL}://project_${CI_PROJECT_ID}_bot:${GITLAB_PROJECT_ACCESS_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/${CI_PROJECT_PATH}.wiki.git"
+
+        # Clone this project's wiki under /tmp
+        - rm -rf "/tmp/${CI_PROJECT_NAME}.wiki"
+        - cd /tmp
+        - git clone "${WIKI_URL}"
+
+        # Enter the cloned repo
+        - cd "${CI_PROJECT_NAME}.wiki"
+
+        # Update the file
+        - mv .git/ ../
+        - rm -rf ./*
+        - mv ../.git/ ./
+        - cp "${CI_PROJECT_DIR}/.gitignore" .
+        - cp -R "${CI_PROJECT_DIR}/${WIKI_FOLDER}/." .
+
+        # Set committer info
+        - git config user.name "$GITLAB_USER_NAME"
+        - git config user.email "$GITLAB_USER_EMAIL"
+
+        # Commit the gitignore file
+        - git add ".gitignore"
+        - git commit -m "Add gitignore file" || true
+
+        # Commit the file
+        - git add .
+        - git commit -m "${VERSION}" || true
+
+        # Push the change back to the master branch of the wiki
+        - git push origin "HEAD:main"
+    rules:
+        -   if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
+
+
+release:gitlab:
+    stage: release
+    tags:
+        - release
+    image: registry.gitlab.com/gitlab-ci-utils/curl-jq:latest
+    script:
+        - !reference [ .get_version, script ]
+        - echo 'Running release_job'
+        - !reference [ .get_packages_url, script ]
+
+        # Extract description from CHANGELOG.md
+        - CHANGELOG_LINE_START=`awk '/##\ [0-9]+\.[0-9]+\.[0-9]+/{print NR; exit;}' CHANGELOG.md`
+        - CHANGELOG_LINE_END=`awk '/##\ [0-9]+\.[0-9]+\.[0-9]+/{ count++; if(count>1) {print NR; exit;}}' CHANGELOG.md`
+        - DESCRIPTION=`awk 'NR > '$CHANGELOG_LINE_START' && NR < '$CHANGELOG_LINE_END'' CHANGELOG.md`
+
+        # Create Release (can't be done by release_step of gitlab image because it don't have access to env var defined in script_step)
+        - >
+            RELEASE_DATA=$(jq --null-input --arg version "$VERSION" --arg description "# Changelog (version $VERSION) $DESCRIPTION" --arg tag_name "$VERSION" --arg ref "$CI_COMMIT_SHORT_SHA" '{
+                "name": $version,
+                "description": $description,
+                "tag_name": $tag_name,
+                "ref": $ref,
+                "assets": {
+                    "links": [
+                        {
+                          "name": "Wiki",
+                          "url": "'${PACKAGE_URL_WIKI}'",
+                        }
+                    ]   
+                }
+            }')
+        - >
+            curl --data "${RELEASE_DATA}" \
+              --header "Content-Type: application/json" \
+              --header "JOB-TOKEN: $CI_JOB_TOKEN" \
+              --request POST "${GITLAB_API_PROJECT_URL}/releases"
+    rules:
+        -   if: '$CI_COMMIT_REF_PROTECTED == "true"'
\ No newline at end of file