diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9821d919157a54c3ca1bffb2f70e2a6b310d5174
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,85 @@
+default:
+  image: $CI_REGISTRY_IMAGE/ci-build-runner:$CI_COMMIT_REF_SLUG
+  before_script:
+    - python --version # For debugging
+  cache:
+    paths:
+      - .cache/pip
+  artifacts:
+    expire_in: 1 week
+
+stages:
+  - prepare
+  - test
+  # - test_data
+  # check if this needs to be a separate step
+  # - build_extensions
+  - package
+  # - integration
+  # - publish # publish instead of deploy
+
+# Caching of dependencies to speed up builds
+variables:
+  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
+
+include:
+  - template: Security/SAST.gitlab-ci.yml
+  - template: Security/Dependency-Scanning.gitlab-ci.yml
+  - template: Security/Secret-Detection.gitlab-ci.yml
+
+# Prepare image to run ci on
+trigger_prepare:
+  stage: prepare
+  trigger:
+    strategy: depend
+    include: .prepare.gitlab-ci.yml
+
+
+sast:
+  variables:
+    SAST_EXCLUDED_ANALYZERS: brakeman, flawfinder, kubesec, nodejs-scan, phpcs-security-audit,
+      pmd-apex, security-code-scan, sobelow, spotbugs
+  stage: test
+
+dependency_scanning:
+ # override default before_script, job won't have Python available
+ before_script:
+   - uname
+
+secret_detection:
+ # override default before_script, job won't have Python available
+ before_script:
+   - uname
+
+
+# Run code coverage on the base image thus also performing unit tests
+run_unit_tests_coverage:
+  stage: test
+  script:
+   - tox -e coverage
+  coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
+  artifacts:
+    reports:
+      coverage_report:
+        coverage_format: cobertura
+        path: coverage.xml
+    paths:
+      - htmlcov/*
+
+package_files:
+  stage: package
+  artifacts:
+    expire_in: 1w
+    paths:
+      - dist/*
+  script:
+    - tox -e build
+
+package_docs:
+  stage: package
+  artifacts:
+    expire_in: 1w
+    paths:
+      - docs/build/*
+  script:
+    - tox -e docs
diff --git a/.prepare.gitlab-ci.yml b/.prepare.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..60ea459a8dfec1bd680e7a7d8b4206044138be37
--- /dev/null
+++ b/.prepare.gitlab-ci.yml
@@ -0,0 +1,25 @@
+stages:
+  - build
+
+build_ci_runner_image:
+  stage: build
+  image:
+    name: docker
+    pull_policy: always
+  tags:
+    - dind
+  script:
+    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+    - |
+      if docker pull $CI_REGISTRY_IMAGE/ci-build-runner:$CI_COMMIT_REF_SLUG; then
+        docker build --cache-from $CI_REGISTRY_IMAGE/ci-build-runner:$CI_COMMIT_REF_SLUG --tag $CI_REGISTRY_IMAGE/ci-build-runner:$CI_COMMIT_REF_SLUG -f docker/ci-runner/Dockerfile .
+      else
+        docker pull $CI_REGISTRY_IMAGE/ci-build-runner:latest || true
+        docker build --cache-from $CI_REGISTRY_IMAGE/ci-build-runner:latest --tag $CI_REGISTRY_IMAGE/ci-build-runner:$CI_COMMIT_REF_SLUG -f docker/ci-runner/Dockerfile .
+      fi
+    - docker push $CI_REGISTRY_IMAGE/ci-build-runner:$CI_COMMIT_REF_SLUG  # push the image
+    - |
+      if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
+        docker image tag $CI_REGISTRY_IMAGE/ci-build-runner:$CI_COMMIT_REF_SLUG $CI_REGISTRY_IMAGE/ci-build-runner:latest
+        docker push $CI_REGISTRY_IMAGE/ci-build-runner:latest
+      fi