diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 036f0f994f3ac980574ebda8319d0e2ae5b93074..e3385bbc5cb858f46142c1640f89c353e498ed47 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -71,3 +71,13 @@ trans_de:
   script:
   - ./update_translations.sh de
   coverage: '/^TOTAL.*\s+(\d+\%)$/'
+
+publish-pip:
+  stage: deploy
+  script:
+  - pip3 install build twine
+  - PACKAGE_VERSION="${CI_COMMIT_TAG#v}" python3 -m build
+  - TWINE_USERNAME="${GITLABPKGS_USERNAME}" TWINE_PASSWORD="${GITLABPKGS_PASSWORD}" python3 -m twine upload --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi dist/*
+  - TWINE_USERNAME="${PYPI_USERNAME}" TWINE_PASSWORD="${PYPI_PASSWORD}" python3 -m twine upload dist/*
+  rules:
+    - if: '$CI_COMMIT_TAG =~ /v[0-9]+[.][0-9]+[.][0-9]+.*/'
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..ead7fb04ff725f357d14ebfa1ee564458f91187f
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,30 @@
+from setuptools import setup, find_packages
+import os
+
+with open('README.md', 'r', encoding='utf-8') as f:
+	long_description = f.read()
+
+setup(
+	name='uffd',
+	version=os.environ.get('PACKAGE_VERSION', 'local'),
+	description='UserFerwaltungsFrontend: Ldap based single sign on and user management web software',
+	long_description=long_description,
+	long_description_content_type='text/markdown',
+	url='https://git.cccv.de/uffd/uffd',
+	classifiers=[
+		'Programming Language :: Python :: 3',
+		'Development Status :: 4 - Beta',
+		'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
+		'Operating System :: OS Independent',
+		'Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP',
+		'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+		'Environment :: Web Environment',
+		'Framework :: Flask',
+	],
+	author='CCCV',
+	author_email='it@cccv.de',
+	license='AGPL3',
+	packages=find_packages(),
+	zip_safe=False,
+	python_requires='>=3.7',
+)