diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 73d6ee549009abead9b7ae5b1051a1c431075f10..6ad7a856a300e80bfc8222008488c2554d8f6954 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,18 +1,22 @@
-stages:          # List of stages for jobs, and their order of execution
-  - build
-  - test
-  - packaging
-
 variables:
   EXLIBRIS_SDK_DIR: "/exlibris"
   ROSETTASDK: "${EXLIBRIS_SDK_DIR}/7.3/dps-sdk-projects/dps-sdk-deposit/lib/" # base: https://github.com/ExLibrisGroup/Rosetta.dps-sdk-projects
-  IMAGE_TARGET: "$CI_REGISTRY_IMAGE/bookworm_xml_plugin4rosetta"
+  DOCKERFILE_DEB: "${CI_PROJECT_DIR}/gitlab-ci/Dockerfile_DEB"
+  IMAGE_TARGET_DEB: "${CI_REGISTRY_IMAGE}/debian12_${CI_PROJECT_NAME}"
+  IMAGE_BASE_DEB: "sdvharbor.slub-dresden.de/replication/debian:bookworm-slim"
+  IMAGE_BASE_ULX: "sdvharbor.slub-dresden.de/replication/ubuntu:jammy"
   FF_USE_FASTZIP: "true"
   # These can be specified per job or per pipeline
   ARTIFACT_COMPRESSION_LEVEL: "fast"
   CACHE_COMPRESSION_LEVEL: "fast"
 #  CI_DEBUG_TRACE: "true"
 
+stages:          # List of stages for jobs, and their order of execution
+  - build-env
+  - test-tool
+  - package-tool
+  - test-package
+
 default:
   image:
     # use this image for all later stages that happen after the build stage
@@ -21,9 +25,9 @@ default:
 before_script:    # These steps are run before EACH job.
   - export ROSETTASDK="${ROSETTASDK}"
 
-build-env-job:       # This job runs in the build stage, which runs first.
-  stage: build
-  timeout: 30m
+.build-env-job:
+  stage: build-env
+  timeout: 10m
   tags:
     - "docker"
   image:
@@ -41,7 +45,7 @@ build-env-job:       # This job runs in the build stage, which runs first.
     - >-
       /kaniko/executor
       --context "${CI_PROJECT_DIR}"
-      --dockerfile "${CI_PROJECT_DIR}/gitlab-ci/Dockerfile"
+      --dockerfile "${DOCKERFILE}"
       --destination "${IMAGE_TARGET}:latest"
       --build-arg "GITDIR=${CI_PROJECT_DIR}"
       --cache=true
@@ -51,23 +55,85 @@ build-env-job:       # This job runs in the build stage, which runs first.
       --use-new-run
       --ignore-var-run
 
-test-job:
-  stage: test
-  timeout: 3h
+build-debian-env-job:
+  extends: .build-env-job
+  variables:
+    DOCKERFILE: ${DOCKERFILE_DEB}
+    IMAGE_TARGET: ${IMAGE_TARGET_DEB}
+
+test-debian-job:
+  stage: test-tool
+  timeout: 5m
   tags:
     - "docker"
+  image:
+    name: "${IMAGE_TARGET_DEB}:latest"
   script:
     - ls -lha /exlibris/
     - ROSETTASDK=$ROSETTASDK make -e check_prerequisites
     - ROSETTASDK=$ROSETTASDK make -e test
 
-packaging-job:
-  stage: packaging
-  timeout: 3h
+package-debian-job:
+  stage: package-tool
+  timeout: 5m
+  image:
+    name: "${IMAGE_TARGET_DEB}:latest"
   tags:
     - "docker"
   script:
-    - ROSETTASDK=$ROSETTASDK make -e
+    # HINT: current working dir == '/builds/digital-preservation/${CI_PROJECT_NAME}' as root
+    # bundle project into jar
+    - ROSETTASDK=${ROSETTASDK} make -e
+    # retrieve version infos
+    - REVISION="1"
+    - BRANCH="$(([ -z "${CI_COMMIT_BRANCH}" ] && echo ${CI_COMMIT_TAG} || echo ${CI_COMMIT_BRANCH}) | sed "s#[^A-Za-z0-9\.~+-]##g")" # use tag name in tag pipelines, filter characters based on https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-version
+    - VERSION="$(git rev-list HEAD --count)-${BRANCH}"
+    - ARCHITECTURE="all"
+    # create build dir structure
+    - DEB_BUILD_DIR="${CI_PROJECT_NAME}_${VERSION}-${REVISION}_${ARCHITECTURE}"
+    - mkdir -p ${DEB_BUILD_DIR}/DEBIAN
+    - DEB_JAR_DIR="${DEB_BUILD_DIR}/usr/lib/java"
+    - mkdir -p ${DEB_JAR_DIR}
+    # copy project files to be packaged
+    - cp ./*.jar ${DEB_JAR_DIR}/
+    # copy & rename deb control file template
+    - cp gitlab-ci/${CI_PROJECT_NAME}.control ${DEB_BUILD_DIR}/DEBIAN/control
+    # set package version
+    - sed -i "s#VERSION_PLACEHOLDER#${VERSION}-${REVISION}#g" ${DEB_BUILD_DIR}/DEBIAN/control
+    - sed -i "s#ARCHITECTURE_PLACEHOLDER#${ARCHITECTURE}#g" "${DEB_BUILD_DIR}/DEBIAN/control"
+    # add checksums
+    - pushd ${DEB_BUILD_DIR}
+    - md5sum $(find * -type f -not -path 'DEBIAN/*') > DEBIAN/md5sums
+    - popd
+    # build binary deb package
+    - dpkg-deb --build --root-owner-group ${DEB_BUILD_DIR}/
   artifacts:
     paths:
-      - ./*.jar
+      # package name: ${CI_PROJECT_NAME}_[VERSION]-[REVISION]_[ARCHITECTURE].deb
+      - "*.deb"
+
+test-install-debian-job:
+  stage: test-package
+  timeout: 5m
+  image:
+    # HINT: debian base image to simulate an installation target
+    name: "${IMAGE_BASE_DEB}"
+  tags:
+    - "docker"
+  script:
+    - apt update
+    - apt install -y ./${CI_PROJECT_NAME}*.deb
+    - java -jar /usr/local/java/XmlFormatValidationPlugin.jar
+
+test-install-ubuntu-job:
+  stage: test-package
+  timeout: 5m
+  image:
+    # HINT: ubuntu base image to simulate an installation target
+    name: "${IMAGE_BASE_ULX}"
+  tags:
+    - "docker"
+  script:
+    - apt update
+    - apt install -y ./${CI_PROJECT_NAME}*.deb
+    - java -jar /usr/local/java/XmlFormatValidationPlugin.jar
\ No newline at end of file
diff --git a/gitlab-ci/Dockerfile b/gitlab-ci/Dockerfile_DEB
similarity index 100%
rename from gitlab-ci/Dockerfile
rename to gitlab-ci/Dockerfile_DEB
diff --git a/gitlab-ci/xml_plugin4rosetta.control b/gitlab-ci/xml_plugin4rosetta.control
new file mode 100644
index 0000000000000000000000000000000000000000..aef778ef7626fbb9115159c6a2de620bc79b9c82
--- /dev/null
+++ b/gitlab-ci/xml_plugin4rosetta.control
@@ -0,0 +1,10 @@
+Package: xml-plugin4rosetta
+Version: VERSION_PLACEHOLDER
+Architecture: ARCHITECTURE_PLACEHOLDER
+Maintainer: SLUBArchiv.digital <langzeitarchiv@slub-dresden.de>
+Description: This is a Java-based XML format validator plugin (for AIS Rosetta) or standalone CLI application.
+Homepage: https://git.slub-dresden.de/digital-preservation/xml_plugin4rosetta
+Section: main
+Priority: optional
+Depends: bash, gawk, mawk, coreutils, file, util-linux, sed, findutils, make, default-jre, libcommons-cli-java
+Recommends: slubarchiv-xml-catalog