diff --git a/Makefile b/Makefile
index 46280260d010d87541f1015c4098ad519dc6de6e_TWFrZWZpbGU=..87bfd17035978600db14c75d02f35ee418eb22ae_TWFrZWZpbGU= 100644
--- a/Makefile
+++ b/Makefile
@@ -296,9 +296,11 @@
 	rm -rf $(PYOX_DIR)/doc
 	cp -a doc $(PYOX_DIR)/doc
 
+pytype-docker:
+	contrib/docker/pytype/recipe.sh
 
 .PHONY: help all local build doc cleanbutpackages clean install install-bin \
 	install-doc install-home install-home-bin install-home-doc \
 	dist dist-notests check tests rust-tests check-code format-c \
 	update-pot pyoxidizer pyoxidizer-windows-tests pyoxidizer-macos-tests \
 	$(packaging_targets) \
@@ -299,7 +301,7 @@
 
 .PHONY: help all local build doc cleanbutpackages clean install install-bin \
 	install-doc install-home install-home-bin install-home-doc \
 	dist dist-notests check tests rust-tests check-code format-c \
 	update-pot pyoxidizer pyoxidizer-windows-tests pyoxidizer-macos-tests \
 	$(packaging_targets) \
-	osx
+	osx pytype-docker
diff --git a/contrib/docker/pytype/Dockerfile b/contrib/docker/pytype/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..87bfd17035978600db14c75d02f35ee418eb22ae_Y29udHJpYi9kb2NrZXIvcHl0eXBlL0RvY2tlcmZpbGU=
--- /dev/null
+++ b/contrib/docker/pytype/Dockerfile
@@ -0,0 +1,14 @@
+FROM registry.heptapod.net/mercurial/ci-images/mercurial-core:v2.0
+
+USER ci-runner
+
+ENV PATH=/home/ci-runner/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+ENV PYTHONPATH=/home/ci-runner/.local/lib/python3.11/site-packages
+
+RUN python3 -m pip install --user --break-system-packages --upgrade pytype==2023.11.21
+
+ADD --chown=ci-runner entrypoint.sh /home/ci-runner/entrypoint.sh
+
+RUN chmod -R a=rwX /home/ci-runner/.local/ /home/ci-runner/entrypoint.sh
+
+CMD /home/ci-runner/entrypoint.sh
diff --git a/contrib/docker/pytype/entrypoint.sh b/contrib/docker/pytype/entrypoint.sh
new file mode 100755
index 0000000000000000000000000000000000000000..87bfd17035978600db14c75d02f35ee418eb22ae_Y29udHJpYi9kb2NrZXIvcHl0eXBlL2VudHJ5cG9pbnQuc2g=
--- /dev/null
+++ b/contrib/docker/pytype/entrypoint.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+cd /tmp/mercurial-ci/
+make local
+./contrib/setup-pytype.sh
+./contrib/check-pytype.sh
diff --git a/contrib/docker/pytype/recipe.sh b/contrib/docker/pytype/recipe.sh
new file mode 100755
index 0000000000000000000000000000000000000000..87bfd17035978600db14c75d02f35ee418eb22ae_Y29udHJpYi9kb2NrZXIvcHl0eXBlL3JlY2lwZS5zaA==
--- /dev/null
+++ b/contrib/docker/pytype/recipe.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+# find repo-root without calling hg as this might be run with sudo
+THIS="$(readlink -m "$0")"
+HERE="$(dirname "$THIS")"
+HG_ROOT="$(readlink -m "$HERE"/../../..)"
+echo source mercurial repository: "$HG_ROOT"
+
+# find actual user as this might be run with sudo
+if [ -n "$SUDO_UID" ]; then
+    ACTUAL_UID="$SUDO_UID"
+else
+    ACTUAL_UID="$(id -u)"
+fi
+if [ -n "$SUDO_GID" ]; then
+    ACTUAL_GID="$SUDO_GID"
+else
+    ACTUAL_GID="$(id -g)"
+fi
+echo using user "$ACTUAL_UID:$ACTUAL_GID"
+if groups | egrep -q '\<(docker|root)\>' ; then
+    env DOCKER_BUILDKIT=1 docker build --tag mercurial-pytype-checker "$HERE"
+    docker run --rm -it --user "$ACTUAL_UID:$ACTUAL_GID" -v "$HG_ROOT:/tmp/mercurial-ci" mercurial-pytype-checker
+else
+    echo "user not in the docker group" >&2
+    echo "(consider running this with \`sudo\`)" >&2
+    exit 255
+fi