diff --git a/dockerfiles/testbadge/Dockerfile b/dockerfiles/testbadge/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..9c03df6f5455192e999a9db003c4ef97b45ad9f6
--- /dev/null
+++ b/dockerfiles/testbadge/Dockerfile
@@ -0,0 +1,9 @@
+FROM python:3-slim
+
+# https://github.com/weiwei/junitparser/pull/89 is in junitparser 2.6.0.
+# https://github.com/weiwei/junitparser/pull/90 is not yet merged :(.
+RUN pip install anybadge "junitparser>=2.6"
+
+COPY testbadge.py /usr/local/bin/testbadge
+
+CMD ["/bin/bash"]
diff --git a/dockerfiles/testbadge/testbadge.py b/dockerfiles/testbadge/testbadge.py
new file mode 100755
index 0000000000000000000000000000000000000000..993c4ffe8d83436aeecc27d99e2069d875f6d956
--- /dev/null
+++ b/dockerfiles/testbadge/testbadge.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+
+import argparse
+
+from anybadge import Badge
+from junitparser import JUnitXml
+
+
+def create_badge(report_path, badge_path):
+    report = JUnitXml.fromfile(report_path)
+    report.update_statistics()
+    total = report.tests - report.skipped
+    passed = total - report.errors - report.failures
+    percentage = 100 * passed / total
+    color = 'green' if passed == total else 'red'
+    badge = Badge(f'tests', f'{percentage:.1f}%', default_color=color)
+    badge.write_badge(badge_path)
+
+
+def main():
+    parser = argparse.ArgumentParser(description='Create a CI badge from a test report.')
+    parser.add_argument('report', metavar='XML', type=str, help='Path of input JUnit XML report file')
+    parser.add_argument('badge', metavar='SVG', type=str, help='Path of output SVG file')
+    args = parser.parse_args()
+    create_badge(args.report, args.badge)
+
+
+if __name__ == '__main__':
+    main()