GitLab CI/CD Integration

GitLab CI/CD provides enterprise-grade continuous integration and deployment workflows that integrate seamlessly with CloudRepo for publishing and consuming artifacts across all supported package types.

This guide covers complete integration patterns for Maven, Gradle, Python (pip, setuptools, Poetry, UV, Pixi), Clojure, and Scala projects, along with advanced pipeline patterns and security best practices.

Benefits & Overview

Why Use CloudRepo with GitLab CI

  • Cost Effective: Save 90%+ compared to JFrog Artifactory with no egress fees

  • Native Integration: Works seamlessly with all build tools and package managers

  • Enterprise Security: Use GitLab CI variables with masking and protection

  • Performance: Leverage GitLab Runner caching for faster builds

  • Support Included: All CloudRepo plans include professional support

  • Compliance Ready: Audit trails and access controls for regulated environments

Repository URL Format

All CloudRepo repositories follow this URL pattern:

https://[organization-id].mycloudrepo.io/repositories/[repository-id]

Replace [organization-id] and [repository-id] with your actual values throughout this guide.

Setting Up GitLab CI Variables

Project-Level Variables

Configure CloudRepo credentials as protected, masked variables:

  1. Navigate to Settings → CI/CD → Variables

  2. Add the following variables:

    • CLOUDREPO_USERNAME: Your CloudRepo username

      • Type: Variable

      • Protected: ✓ (only expose to protected branches/tags)

      • Masked: ✓ (hide value in job logs)

    • CLOUDREPO_PASSWORD: Your CloudRepo password

      • Type: Variable

      • Protected: ✓

      • Masked: ✓

      • Consider using a CloudRepo API token for enhanced security

  3. Optional repository-specific variables:

    • CLOUDREPO_MAVEN_URL: Maven repository URL

    • CLOUDREPO_PYTHON_URL: Python repository URL

    • CLOUDREPO_RAW_URL: Raw repository URL

Group-Level Variables

For multiple projects, configure variables at the group level:

  1. Navigate to Group Settings → CI/CD → Variables

  2. Add the same variables as above

  3. Configure project inheritance as needed

Environment-Specific Variables

Use GitLab environments for staging and production deployments:

.gitlab-ci.yml - Environment-specific deployment
 1stages:
 2  - build
 3  - deploy
 4
 5deploy-staging:
 6  stage: deploy
 7  environment:
 8    name: staging
 9    url: https://staging.example.com
10  variables:
11    CLOUDREPO_URL: $CLOUDREPO_STAGING_URL
12  script:
13    - echo "Deploying to staging repository"
14    - mvn deploy -DaltDeploymentRepository=cloudrepo::default::$CLOUDREPO_URL
15  only:
16    - develop
17
18deploy-production:
19  stage: deploy
20  environment:
21    name: production
22    url: https://production.example.com
23  variables:
24    CLOUDREPO_URL: $CLOUDREPO_PRODUCTION_URL
25  script:
26    - echo "Deploying to production repository"
27    - mvn deploy -DaltDeploymentRepository=cloudrepo::default::$CLOUDREPO_URL
28  only:
29    - tags
30  when: manual

Maven Workflows

Basic Maven Publishing

.gitlab-ci.yml - Basic Maven deployment
 1image: maven:3.9-eclipse-temurin-17
 2
 3variables:
 4  MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
 5  MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"
 6
 7cache:
 8  paths:
 9    - .m2/repository/
10  key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
11
12stages:
13  - build
14  - test
15  - deploy
16
17build:
18  stage: build
19  script:
20    - mvn $MAVEN_CLI_OPTS compile
21  artifacts:
22    paths:
23      - target/
24    expire_in: 1 hour
25
26test:
27  stage: test
28  script:
29    - mvn $MAVEN_CLI_OPTS test
30  artifacts:
31    reports:
32      junit:
33        - target/surefire-reports/TEST-*.xml
34
35deploy:
36  stage: deploy
37  script:
38    - |
39      cat > settings.xml << EOF
40      <settings>
41        <servers>
42          <server>
43            <id>cloudrepo</id>
44            <username>${CLOUDREPO_USERNAME}</username>
45            <password>${CLOUDREPO_PASSWORD}</password>
46          </server>
47        </servers>
48      </settings>
49      EOF
50    - mvn $MAVEN_CLI_OPTS deploy -s settings.xml
51  only:
52    - main
53    - tags

Maven with pom.xml Configuration

Configure your pom.xml for CloudRepo:

pom.xml - Distribution management
 1<project>
 2  <!-- ... other configuration ... -->
 3
 4  <distributionManagement>
 5    <repository>
 6      <id>cloudrepo</id>
 7      <name>CloudRepo Maven Repository</name>
 8      <url>https://myorg.mycloudrepo.io/repositories/maven-releases</url>
 9    </repository>
10    <snapshotRepository>
11      <id>cloudrepo</id>
12      <name>CloudRepo Maven Snapshots</name>
13      <url>https://myorg.mycloudrepo.io/repositories/maven-snapshots</url>
14    </snapshotRepository>
15  </distributionManagement>
16</project>

SNAPSHOT vs Release Workflows

.gitlab-ci.yml - Separate SNAPSHOT and release pipelines
 1deploy-snapshot:
 2  stage: deploy
 3  script:
 4    - echo "Deploying SNAPSHOT to CloudRepo"
 5    - |
 6      cat > settings.xml << EOF
 7      <settings>
 8        <servers>
 9          <server>
10            <id>cloudrepo-snapshots</id>
11            <username>${CLOUDREPO_USERNAME}</username>
12            <password>${CLOUDREPO_PASSWORD}</password>
13          </server>
14        </servers>
15      </settings>
16      EOF
17    - mvn deploy -s settings.xml -DaltSnapshotDeploymentRepository=cloudrepo-snapshots::default::https://myorg.mycloudrepo.io/repositories/maven-snapshots
18  only:
19    - develop
20    - /^feature\/.*$/
21
22deploy-release:
23  stage: deploy
24  script:
25    - echo "Deploying release to CloudRepo"
26    - |
27      # Ensure version doesn't contain SNAPSHOT
28      VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
29      if [[ "$VERSION" == *"SNAPSHOT"* ]]; then
30        echo "Error: Cannot deploy SNAPSHOT version as release"
31        exit 1
32      fi
33    - |
34      cat > settings.xml << EOF
35      <settings>
36        <servers>
37          <server>
38            <id>cloudrepo-releases</id>
39            <username>${CLOUDREPO_USERNAME}</username>
40            <password>${CLOUDREPO_PASSWORD}</password>
41          </server>
42        </servers>
43      </settings>
44      EOF
45    - mvn deploy -s settings.xml -DaltDeploymentRepository=cloudrepo-releases::default::https://myorg.mycloudrepo.io/repositories/maven-releases
46  only:
47    - tags

Multi-Module Maven Projects

.gitlab-ci.yml - Multi-module Maven project
 1image: maven:3.9-eclipse-temurin-17
 2
 3variables:
 4  MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Dorg.slf4j.simpleLogger.showDateTime=true"
 5
 6cache:
 7  paths:
 8    - .m2/repository/
 9  key:
10    files:
11      - pom.xml
12      - "**/pom.xml"
13
14stages:
15  - build
16  - test
17  - deploy
18
19build-all-modules:
20  stage: build
21  script:
22    - mvn clean compile -pl :parent,:module-a,:module-b,:module-common
23  artifacts:
24    paths:
25      - "*/target/"
26    expire_in: 1 hour
27
28test-modules:
29  stage: test
30  parallel:
31    matrix:
32      - MODULE: [module-a, module-b, module-common]
33  script:
34    - mvn test -pl :$MODULE
35  artifacts:
36    reports:
37      junit:
38        - "$MODULE/target/surefire-reports/TEST-*.xml"
39
40deploy-modules:
41  stage: deploy
42  script:
43    - |
44      cat > settings.xml << EOF
45      <settings>
46        <servers>
47          <server>
48            <id>cloudrepo</id>
49            <username>${CLOUDREPO_USERNAME}</username>
50            <password>${CLOUDREPO_PASSWORD}</password>
51          </server>
52        </servers>
53      </settings>
54      EOF
55    - mvn deploy -s settings.xml -DskipTests
56  only:
57    - main
58    - tags

Gradle Workflows

Basic Gradle Publishing

.gitlab-ci.yml - Gradle deployment
 1image: gradle:8.5-jdk17
 2
 3variables:
 4  GRADLE_OPTS: "-Dorg.gradle.daemon=false"
 5  GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"
 6
 7cache:
 8  key: "$CI_COMMIT_REF_SLUG"
 9  paths:
10    - .gradle/caches/
11    - .gradle/wrapper/
12
13stages:
14  - build
15  - test
16  - publish
17
18build:
19  stage: build
20  script:
21    - gradle assemble
22  artifacts:
23    paths:
24      - build/libs/
25    expire_in: 1 hour
26
27test:
28  stage: test
29  script:
30    - gradle test
31  artifacts:
32    reports:
33      junit:
34        - build/test-results/test/**/TEST-*.xml
35
36publish:
37  stage: publish
38  script:
39    - gradle publish -PcloudrepoUsername=$CLOUDREPO_USERNAME -PcloudrepoPassword=$CLOUDREPO_PASSWORD
40  only:
41    - main
42    - tags

Gradle build.gradle Configuration

build.gradle - Publishing configuration
 1plugins {
 2    id 'java'
 3    id 'maven-publish'
 4}
 5
 6group = 'com.example'
 7version = '1.0.0'
 8
 9publishing {
10    publications {
11        maven(MavenPublication) {
12            from components.java
13        }
14    }
15    repositories {
16        maven {
17            name = 'cloudrepo'
18            url = version.endsWith('SNAPSHOT')
19                ? 'https://myorg.mycloudrepo.io/repositories/gradle-snapshots'
20                : 'https://myorg.mycloudrepo.io/repositories/gradle-releases'
21            credentials {
22                username = project.findProperty('cloudrepoUsername') ?: System.getenv('CLOUDREPO_USERNAME')
23                password = project.findProperty('cloudrepoPassword') ?: System.getenv('CLOUDREPO_PASSWORD')
24            }
25        }
26    }
27}

Gradle Kotlin DSL

build.gradle.kts - Kotlin DSL publishing
 1plugins {
 2    kotlin("jvm") version "1.9.22"
 3    `maven-publish`
 4}
 5
 6group = "com.example"
 7version = "1.0.0"
 8
 9publishing {
10    publications {
11        create<MavenPublication>("maven") {
12            from(components["java"])
13        }
14    }
15    repositories {
16        maven {
17            name = "cloudrepo"
18            url = uri(
19                if (version.toString().endsWith("SNAPSHOT")) {
20                    "https://myorg.mycloudrepo.io/repositories/gradle-snapshots"
21                } else {
22                    "https://myorg.mycloudrepo.io/repositories/gradle-releases"
23                }
24            )
25            credentials {
26                username = findProperty("cloudrepoUsername")?.toString()
27                    ?: System.getenv("CLOUDREPO_USERNAME")
28                password = findProperty("cloudrepoPassword")?.toString()
29                    ?: System.getenv("CLOUDREPO_PASSWORD")
30            }
31        }
32    }
33}

Python Workflows

Publishing with pip/setuptools

.gitlab-ci.yml - Python setuptools deployment
 1image: python:3.11
 2
 3cache:
 4  paths:
 5    - .cache/pip
 6  key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
 7
 8stages:
 9  - test
10  - build
11  - publish
12
13test:
14  stage: test
15  script:
16    - pip install -e .[test]
17    - pytest tests/ --junitxml=report.xml
18  artifacts:
19    reports:
20      junit: report.xml
21
22build:
23  stage: build
24  script:
25    - pip install build
26    - python -m build
27  artifacts:
28    paths:
29      - dist/
30    expire_in: 1 hour
31
32publish-to-cloudrepo:
33  stage: publish
34  script:
35    - pip install twine
36    - |
37      cat > ~/.pypirc << EOF
38      [distutils]
39      index-servers = cloudrepo
40
41      [cloudrepo]
42      repository = https://myorg.mycloudrepo.io/repositories/python
43      username = ${CLOUDREPO_USERNAME}
44      password = ${CLOUDREPO_PASSWORD}
45      EOF
46    - twine upload -r cloudrepo dist/*
47  only:
48    - tags

Publishing with Poetry

.gitlab-ci.yml - Poetry deployment
 1image: python:3.11
 2
 3variables:
 4  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
 5  POETRY_HOME: "$CI_PROJECT_DIR/.poetry"
 6  POETRY_VIRTUALENVS_IN_PROJECT: "true"
 7
 8cache:
 9  paths:
10    - .cache/pip
11    - .poetry
12    - .venv
13  key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
14
15before_script:
16  - curl -sSL https://install.python-poetry.org | python3 -
17  - export PATH="$POETRY_HOME/bin:$PATH"
18  - poetry --version
19
20stages:
21  - test
22  - build
23  - publish
24
25test:
26  stage: test
27  script:
28    - poetry install --with test
29    - poetry run pytest tests/ --junitxml=report.xml
30  artifacts:
31    reports:
32      junit: report.xml
33
34build:
35  stage: build
36  script:
37    - poetry build
38  artifacts:
39    paths:
40      - dist/
41    expire_in: 1 hour
42
43publish-to-cloudrepo:
44  stage: publish
45  script:
46    - |
47      poetry config repositories.cloudrepo https://myorg.mycloudrepo.io/repositories/python
48      poetry config http-basic.cloudrepo ${CLOUDREPO_USERNAME} ${CLOUDREPO_PASSWORD}
49    - poetry publish -r cloudrepo
50  only:
51    - tags

Publishing with UV (Ultra-Fast)

.gitlab-ci.yml - UV deployment
 1image: python:3.11
 2
 3variables:
 4  UV_CACHE_DIR: "$CI_PROJECT_DIR/.cache/uv"
 5  UV_SYSTEM_PYTHON: "1"
 6
 7cache:
 8  paths:
 9    - .cache/uv
10  key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
11
12before_script:
13  - curl -LsSf https://astral.sh/uv/install.sh | sh
14  - source $HOME/.cargo/env
15  - uv --version
16
17stages:
18  - test
19  - build
20  - publish
21
22test:
23  stage: test
24  script:
25    - uv pip install -e .[test]
26    - uv run pytest tests/ --junitxml=report.xml
27  artifacts:
28    reports:
29      junit: report.xml
30
31build:
32  stage: build
33  script:
34    - uv pip install build
35    - uv run python -m build
36  artifacts:
37    paths:
38      - dist/
39    expire_in: 1 hour
40
41publish-to-cloudrepo:
42  stage: publish
43  script:
44    - uv pip install twine
45    - |
46      cat > ~/.pypirc << EOF
47      [distutils]
48      index-servers = cloudrepo
49
50      [cloudrepo]
51      repository = https://myorg.mycloudrepo.io/repositories/python
52      username = ${CLOUDREPO_USERNAME}
53      password = ${CLOUDREPO_PASSWORD}
54      EOF
55    - uv run twine upload -r cloudrepo dist/*
56  only:
57    - tags

Publishing with Pixi

.gitlab-ci.yml - Pixi deployment
 1image: condaforge/miniforge3:latest
 2
 3variables:
 4  PIXI_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pixi"
 5
 6cache:
 7  paths:
 8    - .cache/pixi
 9    - .pixi
10  key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
11
12before_script:
13  - curl -fsSL https://pixi.sh/install.sh | bash
14  - export PATH="$HOME/.pixi/bin:$PATH"
15  - pixi --version
16
17stages:
18  - test
19  - build
20  - publish
21
22test:
23  stage: test
24  script:
25    - pixi run test
26  artifacts:
27    reports:
28      junit: test-results.xml
29
30build:
31  stage: build
32  script:
33    - pixi run build
34  artifacts:
35    paths:
36      - dist/
37    expire_in: 1 hour
38
39publish-to-cloudrepo:
40  stage: publish
41  script:
42    - |
43      # Configure pixi to use CloudRepo
44      pixi config set-channel cloudrepo https://myorg.mycloudrepo.io/repositories/conda
45      pixi auth login cloudrepo --username ${CLOUDREPO_USERNAME} --password ${CLOUDREPO_PASSWORD}
46    - pixi upload dist/*.tar.bz2 --channel cloudrepo
47  only:
48    - tags

Wheel and sdist Distribution

.gitlab-ci.yml - Building both wheel and sdist
 1build-distributions:
 2  stage: build
 3  image: python:3.11
 4  script:
 5    - pip install build
 6    - python -m build --sdist --wheel
 7    - ls -la dist/
 8  artifacts:
 9    paths:
10      - dist/*.whl
11      - dist/*.tar.gz
12    expire_in: 1 week
13
14publish-all-formats:
15  stage: publish
16  image: python:3.11
17  dependencies:
18    - build-distributions
19  script:
20    - pip install twine
21    - |
22      # Configure CloudRepo repository
23      cat > ~/.pypirc << EOF
24      [distutils]
25      index-servers = cloudrepo
26
27      [cloudrepo]
28      repository = https://myorg.mycloudrepo.io/repositories/python
29      username = ${CLOUDREPO_USERNAME}
30      password = ${CLOUDREPO_PASSWORD}
31      EOF
32    - |
33      # Upload both wheel and source distribution
34      echo "Uploading wheel and sdist to CloudRepo..."
35      twine upload -r cloudrepo dist/*.whl dist/*.tar.gz
36  only:
37    - tags

Clojure Workflows

Publishing with Leiningen

.gitlab-ci.yml - Leiningen deployment
 1image: clojure:temurin-17-lein-2.11.2
 2
 3cache:
 4  paths:
 5    - ~/.m2/repository
 6  key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
 7
 8stages:
 9  - test
10  - deploy
11
12test:
13  stage: test
14  script:
15    - lein test
16
17deploy-to-cloudrepo:
18  stage: deploy
19  script:
20    - |
21      # Create credentials file
22      mkdir -p ~/.lein
23      cat > ~/.lein/credentials.clj << EOF
24      {#"cloudrepo" {:username "${CLOUDREPO_USERNAME}"
25                     :password "${CLOUDREPO_PASSWORD}"}}
26      EOF
27      chmod 600 ~/.lein/credentials.clj
28    - |
29      # Update project.clj with CloudRepo repository
30      cat >> project.clj << EOF
31      :repositories [["cloudrepo" {:url "https://myorg.mycloudrepo.io/repositories/clojure"
32                                   :sign-releases false}]]
33      :deploy-repositories [["cloudrepo" {:url "https://myorg.mycloudrepo.io/repositories/clojure"
34                                         :sign-releases false
35                                         :creds :gpg}]]
36      EOF
37    - lein deploy cloudrepo
38  only:
39    - main
40    - tags

Publishing with deps.edn and deps-deploy

.gitlab-ci.yml - deps.edn deployment
 1image: clojure:temurin-17-tools-deps
 2
 3cache:
 4  paths:
 5    - ~/.m2/repository
 6    - ~/.gitlibs
 7  key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
 8
 9stages:
10  - test
11  - build
12  - deploy
13
14test:
15  stage: test
16  script:
17    - clojure -M:test
18
19build-jar:
20  stage: build
21  script:
22    - clojure -T:build jar
23  artifacts:
24    paths:
25      - target/*.jar
26    expire_in: 1 hour
27
28deploy-to-cloudrepo:
29  stage: deploy
30  dependencies:
31    - build-jar
32  script:
33    - |
34      # Create deps.edn with deployment configuration
35      cat > deploy-deps.edn << 'EOF'
36      {:deps {slipset/deps-deploy {:mvn/version "0.2.2"}}
37       :aliases
38       {:deploy
39        {:exec-fn deps-deploy.deps-deploy/deploy
40         :exec-args {:installer :remote
41                    :sign-releases? false
42                    :repository {"cloudrepo" {:url "https://myorg.mycloudrepo.io/repositories/clojure"}}
43                    :coordinates {:group/id "your-group"
44                                 :artifact/id "your-artifact"
45                                 :version/string "1.0.0"}
46                    :jar-file "target/your-artifact-1.0.0.jar"}}}}
47      EOF
48    - |
49      # Set up Maven settings for authentication
50      mkdir -p ~/.m2
51      cat > ~/.m2/settings.xml << EOF
52      <settings>
53        <servers>
54          <server>
55            <id>cloudrepo</id>
56            <username>${CLOUDREPO_USERNAME}</username>
57            <password>${CLOUDREPO_PASSWORD}</password>
58          </server>
59        </servers>
60      </settings>
61      EOF
62    - clojure -Sdeps-file deploy-deps.edn -M:deploy
63  only:
64    - tags

Scala Workflows

Publishing with SBT

.gitlab-ci.yml - SBT deployment
 1image: hseeberger/scala-sbt:17.0.2_1.6.2_3.1.1
 2
 3cache:
 4  paths:
 5    - ~/.sbt
 6    - ~/.ivy2/cache
 7    - ~/.cache/coursier
 8  key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
 9
10variables:
11  SBT_OPTS: "-Dsbt.global.base=sbt-cache/.sbtboot -Dsbt.boot.directory=sbt-cache/.boot -Dsbt.ivy.home=sbt-cache/.ivy"
12
13stages:
14  - test
15  - publish
16
17test:
18  stage: test
19  script:
20    - sbt clean test
21
22publish-to-cloudrepo:
23  stage: publish
24  script:
25    - |
26      # Create credentials file
27      mkdir -p ~/.sbt/1.0
28      cat > ~/.sbt/1.0/cloudrepo.credentials << EOF
29      realm=CloudRepo
30      host=myorg.mycloudrepo.io
31      user=${CLOUDREPO_USERNAME}
32      password=${CLOUDREPO_PASSWORD}
33      EOF
34    - sbt "set publishTo := Some(\"CloudRepo\" at \"https://myorg.mycloudrepo.io/repositories/scala\")" publish
35  only:
36    - main
37    - tags

Cross-Compilation for Multiple Scala Versions

.gitlab-ci.yml - Scala cross-compilation
 1image: hseeberger/scala-sbt:17.0.2_1.6.2_3.1.1
 2
 3cache:
 4  paths:
 5    - ~/.sbt
 6    - ~/.ivy2/cache
 7  key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
 8
 9stages:
10  - test
11  - publish
12
13test-cross-versions:
14  stage: test
15  parallel:
16    matrix:
17      - SCALA_VERSION: ["2.12.18", "2.13.12", "3.3.1"]
18  script:
19    - sbt "++$SCALA_VERSION test"
20
21publish-cross-versions:
22  stage: publish
23  script:
24    - |
25      # Configure CloudRepo credentials
26      mkdir -p ~/.sbt/1.0
27      cat > ~/.sbt/1.0/cloudrepo.credentials << EOF
28      realm=CloudRepo
29      host=myorg.mycloudrepo.io
30      user=${CLOUDREPO_USERNAME}
31      password=${CLOUDREPO_PASSWORD}
32      EOF
33    - |
34      # Publish for all Scala versions
35      sbt +publish
36  only:
37    - tags

SBT build.sbt Configuration

build.sbt - Publishing configuration
 1lazy val root = (project in file("."))
 2  .settings(
 3    name := "my-scala-library",
 4    organization := "com.example",
 5    version := "1.0.0",
 6    scalaVersion := "2.13.12",
 7    crossScalaVersions := Seq("2.12.18", "2.13.12", "3.3.1"),
 8
 9    publishTo := {
10      val cloudrepo = "https://myorg.mycloudrepo.io/repositories/"
11      if (isSnapshot.value)
12        Some("snapshots" at cloudrepo + "scala-snapshots")
13      else
14        Some("releases" at cloudrepo + "scala-releases")
15    },
16
17    credentials += Credentials(Path.userHome / ".sbt" / "1.0" / "cloudrepo.credentials"),
18
19    publishMavenStyle := true,
20    publishArtifact in Test := false,
21    pomIncludeRepository := { _ => false }
22  )

Advanced GitLab CI Features

Pipeline Rules and Conditions

.gitlab-ci.yml - Advanced pipeline rules
 1workflow:
 2  rules:
 3    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
 4    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
 5    - if: '$CI_COMMIT_TAG'
 6
 7variables:
 8  DEPLOY_TYPE: "snapshot"
 9
10.deploy-rules:
11  rules:
12    - if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
13      variables:
14        DEPLOY_TYPE: "release"
15        REPOSITORY_URL: "https://myorg.mycloudrepo.io/repositories/releases"
16    - if: '$CI_COMMIT_BRANCH == "develop"'
17      variables:
18        DEPLOY_TYPE: "snapshot"
19        REPOSITORY_URL: "https://myorg.mycloudrepo.io/repositories/snapshots"
20    - if: '$CI_COMMIT_BRANCH =~ /^feature\/.*$/'
21      variables:
22        DEPLOY_TYPE: "feature"
23        REPOSITORY_URL: "https://myorg.mycloudrepo.io/repositories/feature-builds"
24
25deploy:
26  extends: .deploy-rules
27  stage: deploy
28  script:
29    - echo "Deploying $DEPLOY_TYPE to $REPOSITORY_URL"
30    - mvn deploy -DaltDeploymentRepository=cloudrepo::default::$REPOSITORY_URL

Matrix Builds for Multiple Versions

.gitlab-ci.yml - Matrix builds
 1test:
 2  stage: test
 3  parallel:
 4    matrix:
 5      - JAVA: ["11", "17", "21"]
 6        OS: ["ubuntu", "alpine"]
 7  image: openjdk:${JAVA}-${OS}
 8  script:
 9    - ./gradlew test
10
11publish-matrix:
12  stage: deploy
13  parallel:
14    matrix:
15      - TARGET: ["jvm", "android"]
16        VERSION: ["7.0", "8.0"]
17  script:
18    - ./gradlew publish -Ptarget=$TARGET -Pversion=$VERSION

Deployment Environments

.gitlab-ci.yml - Environment deployments
 1stages:
 2  - build
 3  - deploy
 4
 5.deploy-template:
 6  stage: deploy
 7  script:
 8    - echo "Deploying to $CI_ENVIRONMENT_NAME"
 9    - |
10      REPO_URL="https://myorg.mycloudrepo.io/repositories/${CI_ENVIRONMENT_NAME}"
11      mvn deploy -DaltDeploymentRepository=cloudrepo::default::${REPO_URL}
12
13deploy-dev:
14  extends: .deploy-template
15  environment:
16    name: development
17    url: https://dev.example.com
18  only:
19    - develop
20
21deploy-staging:
22  extends: .deploy-template
23  environment:
24    name: staging
25    url: https://staging.example.com
26    on_stop: stop-staging
27  only:
28    - main
29
30deploy-production:
31  extends: .deploy-template
32  environment:
33    name: production
34    url: https://example.com
35  when: manual
36  only:
37    - tags
38
39stop-staging:
40  stage: deploy
41  script:
42    - echo "Stopping staging environment"
43  environment:
44    name: staging
45    action: stop
46  when: manual

Release Automation with Semantic Versioning

.gitlab-ci.yml - Semantic versioning
 1stages:
 2  - version
 3  - build
 4  - release
 5
 6calculate-version:
 7  stage: version
 8  image: node:18
 9  script:
10    - npm install -g semantic-release @semantic-release/git @semantic-release/gitlab
11    - semantic-release --dry-run
12  artifacts:
13    reports:
14      dotenv: version.env
15
16build-and-tag:
17  stage: build
18  dependencies:
19    - calculate-version
20  script:
21    - echo "Building version ${VERSION}"
22    - mvn versions:set -DnewVersion=${VERSION}
23    - mvn clean package
24
25create-release:
26  stage: release
27  image: registry.gitlab.com/gitlab-org/release-cli:latest
28  script:
29    - echo "Creating release ${VERSION}"
30  release:
31    tag_name: 'v${VERSION}'
32    description: 'Release ${VERSION}'
33    assets:
34      links:
35        - name: 'JAR'
36          url: 'https://myorg.mycloudrepo.io/repositories/releases/com/example/app/${VERSION}/app-${VERSION}.jar'

Docker-in-Docker for Containerized Builds

.gitlab-ci.yml - Docker-in-Docker builds
 1image: docker:24
 2
 3services:
 4  - docker:24-dind
 5
 6variables:
 7  DOCKER_HOST: tcp://docker:2376
 8  DOCKER_TLS_CERTDIR: "/certs"
 9  DOCKER_TLS_VERIFY: 1
10  DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
11
12stages:
13  - build
14  - test
15  - publish
16
17build-in-docker:
18  stage: build
19  script:
20    - |
21      # Build application in Docker
22      cat > Dockerfile.build << EOF
23      FROM maven:3.9-eclipse-temurin-17 AS builder
24      WORKDIR /app
25      COPY pom.xml .
26      COPY src ./src
27      RUN mvn clean package -DskipTests
28      EOF
29    - docker build -f Dockerfile.build -t build-env .
30    - docker create --name extract build-env
31    - docker cp extract:/app/target ./target
32    - docker rm extract
33
34test-in-docker:
35  stage: test
36  script:
37    - |
38      docker run --rm \
39        -v ${PWD}:/workspace \
40        -w /workspace \
41        maven:3.9-eclipse-temurin-17 \
42        mvn test
43
44publish-from-docker:
45  stage: publish
46  script:
47    - |
48      docker run --rm \
49        -v ${PWD}:/workspace \
50        -w /workspace \
51        -e CLOUDREPO_USERNAME \
52        -e CLOUDREPO_PASSWORD \
53        maven:3.9-eclipse-temurin-17 \
54        bash -c "
55          cat > settings.xml << 'EOL'
56          <settings>
57            <servers>
58              <server>
59                <id>cloudrepo</id>
60                <username>\${env.CLOUDREPO_USERNAME}</username>
61                <password>\${env.CLOUDREPO_PASSWORD}</password>
62              </server>
63            </servers>
64          </settings>
65          EOL
66          mvn deploy -s settings.xml
67        "
68  only:
69    - tags

Dependency Scanning and Security

.gitlab-ci.yml - Security scanning
 1include:
 2  - template: Security/Dependency-Scanning.gitlab-ci.yml
 3  - template: Security/SAST.gitlab-ci.yml
 4  - template: Security/License-Scanning.gitlab-ci.yml
 5
 6stages:
 7  - build
 8  - test
 9  - deploy
10  - scan
11
12dependency-check:
13  stage: scan
14  image: owasp/dependency-check:latest
15  script:
16    - |
17      /usr/share/dependency-check/bin/dependency-check.sh \
18        --project "$CI_PROJECT_NAME" \
19        --scan . \
20        --format ALL \
21        --out reports
22  artifacts:
23    reports:
24      dependency_scanning: reports/dependency-check-report.json
25    paths:
26      - reports/
27
28license-check:
29  stage: scan
30  image: licensefinder/license_finder:latest
31  script:
32    - license_finder report --format json > licenses.json
33  artifacts:
34    paths:
35      - licenses.json
36
37publish-if-secure:
38  stage: deploy
39  dependencies:
40    - dependency-check
41    - license-check
42  script:
43    - |
44      # Only deploy if security scans pass
45      if [ -f reports/dependency-check-report.json ]; then
46        VULNS=$(jq '.vulnerabilities | length' reports/dependency-check-report.json)
47        if [ "$VULNS" -gt 0 ]; then
48          echo "Found $VULNS vulnerabilities. Blocking deployment."
49          exit 1
50        fi
51      fi
52    - mvn deploy
53  only:
54    - main

Performance Optimization

GitLab Runner Caching

.gitlab-ci.yml - Advanced caching strategies
 1variables:
 2  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
 3  MAVEN_OPTS: >-
 4    -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository
 5    -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN
 6    -Dorg.slf4j.simpleLogger.showDateTime=true
 7    -Djava.awt.headless=true
 8  GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"
 9
10# Global cache configuration
11cache: &global_cache
12  key:
13    files:
14      - pom.xml
15      - package-lock.json
16      - requirements.txt
17      - build.gradle
18    prefix: ${CI_JOB_NAME}
19  paths:
20    - .m2/repository/
21    - .gradle/caches/
22    - .cache/pip/
23    - node_modules/
24  policy: pull-push
25
26.maven-cache:
27  cache:
28    <<: *global_cache
29    paths:
30      - .m2/repository/
31    policy: pull
32
33.gradle-cache:
34  cache:
35    <<: *global_cache
36    paths:
37      - .gradle/caches/
38      - .gradle/wrapper/
39    policy: pull
40
41.python-cache:
42  cache:
43    <<: *global_cache
44    paths:
45      - .cache/pip/
46      - venv/
47    policy: pull
48
49maven-build:
50  extends: .maven-cache
51  stage: build
52  script:
53    - mvn clean compile
54
55gradle-build:
56  extends: .gradle-cache
57  stage: build
58  script:
59    - gradle build
60
61python-build:
62  extends: .python-cache
63  stage: build
64  script:
65    - pip install -r requirements.txt

Artifact Management

.gitlab-ci.yml - Efficient artifact handling
 1stages:
 2  - build
 3  - test
 4  - package
 5  - deploy
 6
 7build:
 8  stage: build
 9  script:
10    - mvn clean compile
11  artifacts:
12    paths:
13      - target/classes/
14    expire_in: 2 hours
15    when: always
16
17test:
18  stage: test
19  dependencies:
20    - build
21  script:
22    - mvn test
23  artifacts:
24    reports:
25      junit:
26        - target/surefire-reports/TEST-*.xml
27      coverage_report:
28        coverage_format: cobertura
29        path: target/site/cobertura/coverage.xml
30    paths:
31      - target/test-classes/
32    expire_in: 1 day
33    when: always
34
35package:
36  stage: package
37  dependencies:
38    - build
39    - test
40  script:
41    - mvn package -DskipTests
42  artifacts:
43    paths:
44      - target/*.jar
45      - target/*.war
46    expire_in: 1 week
47    when: on_success
48
49deploy:
50  stage: deploy
51  dependencies:
52    - package
53  script:
54    - |
55      # Only download required artifacts
56      echo "Deploying artifacts to CloudRepo"
57      for artifact in target/*.jar target/*.war; do
58        if [ -f "$artifact" ]; then
59          curl -u ${CLOUDREPO_USERNAME}:${CLOUDREPO_PASSWORD} \
60            -T "$artifact" \
61            https://myorg.mycloudrepo.io/repositories/releases/
62        fi
63      done
64  only:
65    - tags

Parallel Jobs

.gitlab-ci.yml - Parallel execution
 1stages:
 2  - build
 3  - test
 4  - deploy
 5
 6build:
 7  stage: build
 8  parallel: 3
 9  script:
10    - |
11      # Split build across parallel jobs
12      MODULES=(module-a module-b module-c module-d module-e module-f)
13      TOTAL=${#MODULES[@]}
14      PER_JOB=$(( (TOTAL + CI_NODE_TOTAL - 1) / CI_NODE_TOTAL ))
15      START=$(( (CI_NODE_INDEX - 1) * PER_JOB ))
16      END=$(( START + PER_JOB ))
17
18      for i in $(seq $START $((END - 1))); do
19        if [ $i -lt $TOTAL ]; then
20          MODULE=${MODULES[$i]}
21          echo "Building $MODULE"
22          mvn clean package -pl :$MODULE -am
23        fi
24      done
25
26test-suite:
27  stage: test
28  parallel:
29    matrix:
30      - TEST_SUITE: [unit, integration, e2e]
31        BROWSER: [chrome, firefox]
32  script:
33    - |
34      echo "Running $TEST_SUITE tests with $BROWSER"
35      mvn test -Dtest.suite=$TEST_SUITE -Dbrowser=$BROWSER
36
37deploy-regions:
38  stage: deploy
39  parallel:
40    matrix:
41      - REGION: [us-east-1, eu-west-1, ap-southeast-1]
42  script:
43    - |
44      REPO_URL="https://${REGION}.myorg.mycloudrepo.io/repositories/releases"
45      mvn deploy -DaltDeploymentRepository=cloudrepo::default::${REPO_URL}

DAG Pipelines

.gitlab-ci.yml - Directed Acyclic Graph pipelines
 1stages:
 2  - build
 3  - test
 4  - deploy
 5
 6build-core:
 7  stage: build
 8  script:
 9    - mvn clean compile -pl :core
10
11build-api:
12  stage: build
13  needs: ["build-core"]
14  script:
15    - mvn clean compile -pl :api
16
17build-web:
18  stage: build
19  needs: ["build-api"]
20  script:
21    - mvn clean compile -pl :web
22
23build-cli:
24  stage: build
25  needs: ["build-api"]
26  script:
27    - mvn clean compile -pl :cli
28
29test-unit:
30  stage: test
31  needs: ["build-core"]
32  script:
33    - mvn test -pl :core
34
35test-integration:
36  stage: test
37  needs: ["build-api", "build-web"]
38  script:
39    - mvn verify -Pintegration
40
41test-e2e:
42  stage: test
43  needs: ["build-web", "build-cli"]
44  script:
45    - mvn verify -Pe2e
46
47deploy-all:
48  stage: deploy
49  needs:
50    - job: test-unit
51      artifacts: false
52    - job: test-integration
53      artifacts: false
54    - job: test-e2e
55      artifacts: false
56  script:
57    - mvn deploy

Troubleshooting Common Issues

Authentication Failures

Problem: 401 Unauthorized errors when publishing

Solutions:

  1. Verify credentials are correctly set in GitLab CI variables

  2. Check if variables are marked as protected (only available on protected branches/tags)

  3. Ensure credentials are properly masked to avoid exposure

  4. Test authentication with curl:

curl -u ${CLOUDREPO_USERNAME}:${CLOUDREPO_PASSWORD} \
  https://myorg.mycloudrepo.io/repositories/maven-releases/

Certificate Issues

Problem: SSL certificate verification failures

Solutions:

  1. For self-signed certificates, add to GitLab Runner’s trust store:

before_script:
  - apt-get update && apt-get install -y ca-certificates
  - cp /path/to/cert.crt /usr/local/share/ca-certificates/
  - update-ca-certificates
  1. For testing only (not recommended for production):

variables:
  MAVEN_OPTS: "-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true"

Large File Uploads

Problem: Timeout or failure when uploading large artifacts

Solutions:

  1. Increase GitLab Runner timeout:

deploy:
  timeout: 2 hours
  script:
    - mvn deploy
  1. Use chunked upload for very large files:

split -b 100M large-artifact.jar chunk_
for chunk in chunk_*; do
  curl -u ${CLOUDREPO_USERNAME}:${CLOUDREPO_PASSWORD} \
    -T $chunk \
    https://myorg.mycloudrepo.io/repositories/raw/$chunk
done

Cache Corruption

Problem: Build failures due to corrupted cache

Solutions:

  1. Clear cache in GitLab UI: CI/CD → Pipelines → Clear Runner Caches

  2. Use cache versioning:

cache:
  key: "v2-${CI_JOB_NAME}-${CI_COMMIT_REF_SLUG}"
  1. Implement cache validation:

before_script:
  - |
    if [ -d .m2/repository ]; then
      find .m2/repository -name "*.jar" -exec jar tf {} \; > /dev/null 2>&1 || {
        echo "Cache corrupted, clearing..."
        rm -rf .m2/repository
      }
    fi

Pipeline Performance

Problem: Slow pipeline execution

Solutions:

  1. Use DAG pipelines to parallelize independent jobs

  2. Implement selective testing based on changes:

test:
  script:
    - |
      CHANGED_FILES=$(git diff --name-only HEAD~1)
      if echo "$CHANGED_FILES" | grep -q "^core/"; then
        mvn test -pl :core
      fi
  1. Use GitLab’s distributed cache:

cache:
  key: "$CI_JOB_NAME"
  paths:
    - .m2/repository/
  policy: pull
  untracked: false
  when: on_success

Best Practices Summary

Security

  • Always use masked, protected variables for credentials

  • Rotate credentials regularly

  • Use least-privilege access for CI/CD service accounts

  • Enable dependency scanning and security pipelines

  • Never commit credentials to repository

Performance

  • Leverage caching aggressively but validate cache integrity

  • Use parallel jobs and matrix builds for multi-version testing

  • Implement DAG pipelines for complex workflows

  • Set appropriate artifact expiration times

  • Use shallow clones for faster checkout

Reliability

  • Implement retry logic for network operations

  • Use atomic deployments (all or nothing)

  • Version your artifacts properly (especially for SNAPSHOT vs releases)

  • Maintain separate repositories for different artifact types

  • Test pipeline changes in feature branches first

Maintainability

  • Use YAML anchors and templates to reduce duplication

  • Document pipeline behavior with comments

  • Keep credentials and configuration external to pipeline definitions

  • Use semantic versioning for releases

  • Implement proper logging and error reporting

See also