diff --git a/.gitignore b/.gitignore
index 36f3fb9350969b022ed205b1449ecc07d628b802..8edad8791ad08a32febc5e3dd167557e737164b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,7 @@ artifacts
 *.d.mk
 templates
 /*.apkg
+hanzi-data
+.data-test-ok
+.html-test-ok
+.lint-ok
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6865d465db36274abbb85caec3c22ed7ddfa0ac3
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,79 @@
+image: durcheinander/sinologie-anki-pack-build:latest
+
+stages:
+  - build
+  - release
+
+default:
+  cache:
+    paths:
+      - node_modules
+
+build:
+  stage: build
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "schedule"
+      when: never
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+    - if: $CI_COMMIT_TAG
+  script:
+    - NPM_VERSION=$(grep '"version":' package.json -m 1 | cut -d '"' -f 4)
+    - RELEASE_TAR=card-templates-$NPM_VERSION.tar.gz
+    - RELEASE_TAR_URL="https://gitlab.phaidra.org/kartenaale/card-templates/-/jobs/$CI_JOB_ID/artifacts/raw/$RELEASE_TAR"
+    - BUILD_PREFIX="" make $RELEASE_TAR
+    - echo BUILD_JOB_ID=$CI_JOB_ID > build.env
+    - echo NPM_VERSION=$NPM_VERSION >> build.env
+    - echo RELEASE_DIR=$RELEASE_DIR >> build.env
+    - echo RELEASE_TAR_URL=$RELEASE_TAR_URL >> build.env
+  artifacts:
+    paths:
+      - sinologie-anki-pack-*
+      - ANNOUNCEMENT
+    reports:
+      dotenv: build.env
+
+release-package-json-version-as-git-tag:
+  stage: release
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "schedule"
+      when: never
+    # tagging should only happen after the thing was merged, so do it on the main branch build
+    - if: $CI_COMMIT_BRANCH == "main"
+  # we don't need anything in node_modules or python, so don't fetch the cache
+  cache: []
+  script:
+    - NPM_VERSION=$(grep '"version":' package.json -m 1 | cut -d '"' -f 4)
+    # || exit_code=$? is the recommended way to ignore a non-zero exit code
+    - NPM_VERSION_GIT_TAG=$(git tag | grep ^${NPM_VERSION}$ || exit_code=$?)
+    - if [ -z "$NPM_VERSION_GIT_TAG" ]; then
+    -     echo adding git tag for first commit on main with NPM version ${NPM_VERSION}
+    -     git remote remove origin
+    -     git remote add origin https://oauth:${REPOSITORY_ACCESS_TOKEN}@gitlab.phaidra.org/kartenaale/sinologie-anki-pack.git
+    -     git config user.email Cao Cao
+    -     git config user.name cao.cao@ci.kartenaale
+    -     git tag -a $NPM_VERSION -m "Release $NPM_VERSION"
+    -     git push origin $NPM_VERSION
+    - else
+    - '   echo package-json-tag: keeping older tag for $NPM_VERSION'
+    - fi
+
+create-gitlab-release:
+  image: registry.gitlab.com/gitlab-org/release-cli:latest
+  stage: release
+  rules:
+    - if: $CI_COMMIT_TAG
+  needs:
+    - job: build
+      artifacts: true
+  variables:
+    # we just need the artifacts and don't need up-to-date source, so don't even fetch
+    GIT_STRATEGY: none
+  # we don't need anything in node_modules or python, so don't fetch the cache
+  cache: []
+  release:
+    tag_name: '$CI_COMMIT_TAG'
+    name: 'Card Templates $CI_COMMIT_TAG'
+    assets:
+      links:
+        - name: '$RELEASE_TAR'
+          url: '$RELEASE_TAR_URL'
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..94619726bee8afc4d8dbac4f7610df355ce99f32
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,19 @@
+## dockerfile used for the gitlab build runner
+
+# we are using debian because alpine cannot install anki via pipenv for some reason
+FROM debian:12.4
+
+RUN apt-get update && apt-get install -y \
+faketime \
+git \
+make \
+nodejs \
+npm \
+pipenv \
+python3 \
+python3-pip \
+zip
+
+RUN npm install --global \
+yarn \
+mudslide