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:
Navigate to Settings → CI/CD → Variables
Add the following variables:
CLOUDREPO_USERNAME
: Your CloudRepo usernameType: Variable
Protected: ✓ (only expose to protected branches/tags)
Masked: ✓ (hide value in job logs)
CLOUDREPO_PASSWORD
: Your CloudRepo passwordType: Variable
Protected: ✓
Masked: ✓
Consider using a CloudRepo API token for enhanced security
Optional repository-specific variables:
CLOUDREPO_MAVEN_URL
: Maven repository URLCLOUDREPO_PYTHON_URL
: Python repository URLCLOUDREPO_RAW_URL
: Raw repository URL
Group-Level Variables
For multiple projects, configure variables at the group level:
Navigate to Group Settings → CI/CD → Variables
Add the same variables as above
Configure project inheritance as needed
Environment-Specific Variables
Use GitLab environments for staging and production deployments:
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
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:
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
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
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
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
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
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
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
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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:
Verify credentials are correctly set in GitLab CI variables
Check if variables are marked as protected (only available on protected branches/tags)
Ensure credentials are properly masked to avoid exposure
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:
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
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:
Increase GitLab Runner timeout:
deploy:
timeout: 2 hours
script:
- mvn deploy
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:
Clear cache in GitLab UI: CI/CD → Pipelines → Clear Runner Caches
Use cache versioning:
cache:
key: "v2-${CI_JOB_NAME}-${CI_COMMIT_REF_SLUG}"
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:
Use DAG pipelines to parallelize independent jobs
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
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
Continuous Integration and Deployment - General CI/CD concepts
GitHub Actions CI/CD Integration - GitHub Actions integration
BitBucket Pipelines - Bitbucket Pipelines integration
Maven Repositories - Maven repository details
Python Repositories - Python repository configuration