MuleSoft

MuleSoft CI/CD Pipeline with Maven and GitHub Actions

DeviDevs Team
11 min read
#mulesoft#cicd#maven#github-actions#devops#automation

Automating MuleSoft deployments with CI/CD improves reliability and accelerates delivery. This guide covers building a complete pipeline with Maven and GitHub Actions for CloudHub and Runtime Fabric.

CI/CD Architecture Overview

┌─────────────────────────────────────────────────────────────────────────┐
│                    MULESOFT CI/CD PIPELINE                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   DEVELOPER                    GITHUB                                   │
│  ┌─────────────┐            ┌────────────────────────────────────────┐ │
│  │   Commit    │───push────►│                                        │ │
│  │   Code      │            │   ┌──────────────────────────────────┐ │ │
│  └─────────────┘            │   │     GITHUB ACTIONS WORKFLOW      │ │ │
│                             │   │                                  │ │ │
│                             │   │  1. Checkout Code                │ │ │
│                             │   │  2. Setup JDK 8/11               │ │ │
│                             │   │  3. Maven Cache                  │ │ │
│                             │   │  4. Run MUnit Tests              │ │ │
│                             │   │  5. Package Application          │ │ │
│                             │   │  6. Deploy to Anypoint           │ │ │
│                             │   │                                  │ │ │
│                             │   └──────────────────────────────────┘ │ │
│                             │              │                         │ │
│                             └──────────────┼─────────────────────────┘ │
│                                            │                           │
│                                            ▼                           │
│   ANYPOINT PLATFORM        ┌────────────────────────────────────────┐ │
│  ┌─────────────────────────┤         DEPLOYMENT TARGETS             │ │
│  │                         └────────────────────────────────────────┘ │
│  │   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐            │
│  │   │  CloudHub   │   │  CloudHub   │   │   Runtime   │            │
│  │   │   (DEV)     │   │   (PROD)    │   │   Fabric    │            │
│  │   └─────────────┘   └─────────────┘   └─────────────┘            │
│  └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘

Maven Project Configuration

pom.xml Setup

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>com.devidevs</groupId>
    <artifactId>orders-api</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>mule-application</packaging>
 
    <name>Orders API</name>
    <description>Order Management System API</description>
 
    <properties>
        <!-- Mule Runtime Version -->
        <app.runtime>4.6.0</app.runtime>
 
        <!-- Plugin Versions -->
        <mule.maven.plugin.version>4.1.1</mule.maven.plugin.version>
        <munit.version>3.1.0</munit.version>
 
        <!-- Deployment Properties -->
        <cloudhub.environment>Sandbox</cloudhub.environment>
        <cloudhub.region>us-east-1</cloudhub.region>
        <cloudhub.workers>1</cloudhub.workers>
        <cloudhub.workerType>MICRO</cloudhub.workerType>
 
        <!-- Connected App Credentials (from CI/CD secrets) -->
        <anypoint.client.id>${env.ANYPOINT_CLIENT_ID}</anypoint.client.id>
        <anypoint.client.secret>${env.ANYPOINT_CLIENT_SECRET}</anypoint.client.secret>
        <anypoint.org.id>${env.ANYPOINT_ORG_ID}</anypoint.org.id>
    </properties>
 
    <build>
        <plugins>
            <!-- Mule Maven Plugin -->
            <plugin>
                <groupId>org.mule.tools.maven</groupId>
                <artifactId>mule-maven-plugin</artifactId>
                <version>${mule.maven.plugin.version}</version>
                <extensions>true</extensions>
                <configuration>
                    <!-- CloudHub Deployment Configuration -->
                    <cloudHubDeployment>
                        <uri>https://anypoint.mulesoft.com</uri>
                        <muleVersion>${app.runtime}</muleVersion>
                        <connectedAppClientId>${anypoint.client.id}</connectedAppClientId>
                        <connectedAppClientSecret>${anypoint.client.secret}</connectedAppClientSecret>
                        <connectedAppGrantType>client_credentials</connectedAppGrantType>
                        <applicationName>${project.artifactId}-${cloudhub.environment}</applicationName>
                        <environment>${cloudhub.environment}</environment>
                        <businessGroupId>${anypoint.org.id}</businessGroupId>
                        <region>${cloudhub.region}</region>
                        <workers>${cloudhub.workers}</workers>
                        <workerType>${cloudhub.workerType}</workerType>
                        <objectStoreV2>true</objectStoreV2>
                        <persistentQueues>true</persistentQueues>
                        <properties>
                            <env>${cloudhub.environment}</env>
                            <api.autodiscovery.id>${api.id}</api.autodiscovery.id>
                        </properties>
                        <secureProperties>
                            <db.password>${env.DB_PASSWORD}</db.password>
                            <api.client.secret>${env.API_CLIENT_SECRET}</api.client.secret>
                        </secureProperties>
                    </cloudHubDeployment>
                </configuration>
            </plugin>
 
            <!-- MUnit Plugin -->
            <plugin>
                <groupId>com.mulesoft.munit.tools</groupId>
                <artifactId>munit-maven-plugin</artifactId>
                <version>${munit.version}</version>
                <executions>
                    <execution>
                        <id>test</id>
                        <phase>test</phase>
                        <goals>
                            <goal>test</goal>
                            <goal>coverage-report</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <coverage>
                        <runCoverage>true</runCoverage>
                        <failBuild>true</failBuild>
                        <requiredApplicationCoverage>80</requiredApplicationCoverage>
                        <requiredFlowCoverage>80</requiredFlowCoverage>
                        <formats>
                            <format>html</format>
                            <format>json</format>
                        </formats>
                    </coverage>
                    <runtimeVersion>${app.runtime}</runtimeVersion>
                </configuration>
            </plugin>
        </plugins>
    </build>
 
    <!-- Maven Repositories -->
    <repositories>
        <repository>
            <id>anypoint-exchange-v3</id>
            <name>Anypoint Exchange</name>
            <url>https://maven.anypoint.mulesoft.com/api/v3/maven</url>
            <layout>default</layout>
        </repository>
        <repository>
            <id>mulesoft-releases</id>
            <name>MuleSoft Releases Repository</name>
            <url>https://repository.mulesoft.org/releases/</url>
            <layout>default</layout>
        </repository>
    </repositories>
 
    <pluginRepositories>
        <pluginRepository>
            <id>mulesoft-releases</id>
            <name>MuleSoft Releases Repository</name>
            <url>https://repository.mulesoft.org/releases/</url>
            <layout>default</layout>
        </pluginRepository>
    </pluginRepositories>
 
    <!-- Deployment Profiles -->
    <profiles>
        <!-- Development Profile -->
        <profile>
            <id>dev</id>
            <properties>
                <cloudhub.environment>Sandbox</cloudhub.environment>
                <cloudhub.workers>1</cloudhub.workers>
                <cloudhub.workerType>MICRO</cloudhub.workerType>
            </properties>
        </profile>
 
        <!-- QA Profile -->
        <profile>
            <id>qa</id>
            <properties>
                <cloudhub.environment>QA</cloudhub.environment>
                <cloudhub.workers>1</cloudhub.workers>
                <cloudhub.workerType>SMALL</cloudhub.workerType>
            </properties>
        </profile>
 
        <!-- Production Profile -->
        <profile>
            <id>prod</id>
            <properties>
                <cloudhub.environment>Production</cloudhub.environment>
                <cloudhub.workers>2</cloudhub.workers>
                <cloudhub.workerType>SMALL</cloudhub.workerType>
                <cloudhub.region>us-east-1</cloudhub.region>
            </properties>
        </profile>
 
        <!-- Runtime Fabric Profile -->
        <profile>
            <id>rtf</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.mule.tools.maven</groupId>
                        <artifactId>mule-maven-plugin</artifactId>
                        <version>${mule.maven.plugin.version}</version>
                        <configuration>
                            <runtimeFabricDeployment>
                                <uri>https://anypoint.mulesoft.com</uri>
                                <muleVersion>${app.runtime}</muleVersion>
                                <connectedAppClientId>${anypoint.client.id}</connectedAppClientId>
                                <connectedAppClientSecret>${anypoint.client.secret}</connectedAppClientSecret>
                                <connectedAppGrantType>client_credentials</connectedAppGrantType>
                                <applicationName>${project.artifactId}</applicationName>
                                <target>${rtf.target}</target>
                                <environment>${rtf.environment}</environment>
                                <businessGroupId>${anypoint.org.id}</businessGroupId>
                                <provider>MC</provider>
                                <replicas>2</replicas>
                                <publicUrl>${rtf.public.url}</publicUrl>
                                <lastMileSecurity>true</lastMileSecurity>
                                <clusteringEnabled>true</clusteringEnabled>
                                <deploymentSettings>
                                    <http>
                                        <inbound>
                                            <publicUrl>${rtf.public.url}</publicUrl>
                                        </inbound>
                                    </http>
                                    <resources>
                                        <cpu>
                                            <limit>500m</limit>
                                            <reserved>100m</reserved>
                                        </cpu>
                                        <memory>
                                            <limit>1000Mi</limit>
                                            <reserved>500Mi</reserved>
                                        </memory>
                                    </resources>
                                </deploymentSettings>
                            </runtimeFabricDeployment>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
 
</project>

Maven Settings (settings.xml)

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
          https://maven.apache.org/xsd/settings-1.0.0.xsd">
 
    <servers>
        <!-- Anypoint Exchange Server -->
        <server>
            <id>anypoint-exchange-v3</id>
            <username>~~~Client~~~</username>
            <password>${env.ANYPOINT_CLIENT_ID}~?~${env.ANYPOINT_CLIENT_SECRET}</password>
        </server>
 
        <!-- MuleSoft Releases -->
        <server>
            <id>mulesoft-releases</id>
            <username>${env.MULESOFT_NEXUS_USERNAME}</username>
            <password>${env.MULESOFT_NEXUS_PASSWORD}</password>
        </server>
 
        <!-- Private Exchange Repository -->
        <server>
            <id>exchange-private</id>
            <username>~~~Client~~~</username>
            <password>${env.ANYPOINT_CLIENT_ID}~?~${env.ANYPOINT_CLIENT_SECRET}</password>
        </server>
    </servers>
 
    <profiles>
        <profile>
            <id>mulesoft</id>
            <repositories>
                <repository>
                    <id>anypoint-exchange-v3</id>
                    <name>Anypoint Exchange</name>
                    <url>https://maven.anypoint.mulesoft.com/api/v3/maven</url>
                </repository>
                <repository>
                    <id>mulesoft-releases</id>
                    <name>MuleSoft Releases</name>
                    <url>https://repository.mulesoft.org/releases/</url>
                </repository>
            </repositories>
            <pluginRepositories>
                <pluginRepository>
                    <id>mulesoft-releases</id>
                    <name>MuleSoft Releases</name>
                    <url>https://repository.mulesoft.org/releases/</url>
                </pluginRepository>
            </pluginRepositories>
        </profile>
    </profiles>
 
    <activeProfiles>
        <activeProfile>mulesoft</activeProfile>
    </activeProfiles>
 
</settings>

GitHub Actions Workflows

Main CI/CD Workflow

# .github/workflows/mule-cicd.yml
name: MuleSoft CI/CD Pipeline
 
on:
  push:
    branches: [main, develop, 'release/*']
  pull_request:
    branches: [main, develop]
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deployment environment'
        required: true
        default: 'dev'
        type: choice
        options:
          - dev
          - qa
          - prod
 
env:
  JAVA_VERSION: '11'
  MAVEN_VERSION: '3.9.5'
 
jobs:
  # Build and Test Job
  build-and-test:
    name: Build and Test
    runs-on: ubuntu-latest
 
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
 
      - name: Set up JDK ${{ env.JAVA_VERSION }}
        uses: actions/setup-java@v4
        with:
          java-version: ${{ env.JAVA_VERSION }}
          distribution: 'temurin'
          cache: maven
 
      - name: Configure Maven Settings
        run: |
          mkdir -p ~/.m2
          cat << EOF > ~/.m2/settings.xml
          <settings>
            <servers>
              <server>
                <id>anypoint-exchange-v3</id>
                <username>~~~Client~~~</username>
                <password>${ANYPOINT_CLIENT_ID}~?~${ANYPOINT_CLIENT_SECRET}</password>
              </server>
              <server>
                <id>mulesoft-releases</id>
                <username>${MULESOFT_NEXUS_USERNAME}</username>
                <password>${MULESOFT_NEXUS_PASSWORD}</password>
              </server>
            </servers>
          </settings>
          EOF
        env:
          ANYPOINT_CLIENT_ID: ${{ secrets.ANYPOINT_CLIENT_ID }}
          ANYPOINT_CLIENT_SECRET: ${{ secrets.ANYPOINT_CLIENT_SECRET }}
          MULESOFT_NEXUS_USERNAME: ${{ secrets.MULESOFT_NEXUS_USERNAME }}
          MULESOFT_NEXUS_PASSWORD: ${{ secrets.MULESOFT_NEXUS_PASSWORD }}
 
      - name: Cache Maven Dependencies
        uses: actions/cache@v4
        with:
          path: |
            ~/.m2/repository
            !~/.m2/repository/com/devidevs
          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
          restore-keys: |
            ${{ runner.os }}-maven-
 
      - name: Build Application
        run: mvn clean compile -DskipTests
 
      - name: Run MUnit Tests
        run: mvn test
        env:
          ANYPOINT_CLIENT_ID: ${{ secrets.ANYPOINT_CLIENT_ID }}
          ANYPOINT_CLIENT_SECRET: ${{ secrets.ANYPOINT_CLIENT_SECRET }}
 
      - name: Upload Test Results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results
          path: |
            target/surefire-reports/
            target/munit-reports/
 
      - name: Upload Coverage Report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: coverage-report
          path: target/site/munit/coverage/
 
      - name: Package Application
        run: mvn package -DskipTests
 
      - name: Upload Artifact
        uses: actions/upload-artifact@v4
        with:
          name: mule-artifact
          path: target/*.jar
          retention-days: 5
 
  # Security Scan Job
  security-scan:
    name: Security Scan
    runs-on: ubuntu-latest
    needs: build-and-test
 
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
 
      - name: Run OWASP Dependency Check
        uses: dependency-check/Dependency-Check_Action@main
        with:
          project: 'mulesoft-api'
          path: '.'
          format: 'HTML'
          args: >
            --failOnCVSS 7
            --enableRetired
 
      - name: Upload Security Report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: security-report
          path: reports/
 
  # Deploy to Development
  deploy-dev:
    name: Deploy to Development
    runs-on: ubuntu-latest
    needs: [build-and-test, security-scan]
    if: github.ref == 'refs/heads/develop' || github.event.inputs.environment == 'dev'
    environment:
      name: development
      url: https://orders-api-dev.cloudhub.io
 
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
 
      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          java-version: ${{ env.JAVA_VERSION }}
          distribution: 'temurin'
          cache: maven
 
      - name: Configure Maven Settings
        run: |
          mkdir -p ~/.m2
          cat << EOF > ~/.m2/settings.xml
          <settings>
            <servers>
              <server>
                <id>anypoint-exchange-v3</id>
                <username>~~~Client~~~</username>
                <password>${ANYPOINT_CLIENT_ID}~?~${ANYPOINT_CLIENT_SECRET}</password>
              </server>
            </servers>
          </settings>
          EOF
        env:
          ANYPOINT_CLIENT_ID: ${{ secrets.ANYPOINT_CLIENT_ID }}
          ANYPOINT_CLIENT_SECRET: ${{ secrets.ANYPOINT_CLIENT_SECRET }}
 
      - name: Deploy to CloudHub (Dev)
        run: |
          mvn deploy -DmuleDeploy -Pdev -DskipTests \
            -Danypoint.client.id=${{ secrets.ANYPOINT_CLIENT_ID }} \
            -Danypoint.client.secret=${{ secrets.ANYPOINT_CLIENT_SECRET }} \
            -Danypoint.org.id=${{ secrets.ANYPOINT_ORG_ID }} \
            -Denv.DB_PASSWORD=${{ secrets.DEV_DB_PASSWORD }} \
            -Denv.API_CLIENT_SECRET=${{ secrets.DEV_API_CLIENT_SECRET }}
        env:
          ANYPOINT_CLIENT_ID: ${{ secrets.ANYPOINT_CLIENT_ID }}
          ANYPOINT_CLIENT_SECRET: ${{ secrets.ANYPOINT_CLIENT_SECRET }}
 
      - name: Verify Deployment
        run: |
          echo "Waiting for deployment to complete..."
          sleep 60
          curl -f https://orders-api-dev.cloudhub.io/api/health || exit 1
 
  # Deploy to QA
  deploy-qa:
    name: Deploy to QA
    runs-on: ubuntu-latest
    needs: deploy-dev
    if: github.ref == 'refs/heads/develop' || github.event.inputs.environment == 'qa'
    environment:
      name: qa
      url: https://orders-api-qa.cloudhub.io
 
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
 
      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          java-version: ${{ env.JAVA_VERSION }}
          distribution: 'temurin'
          cache: maven
 
      - name: Configure Maven Settings
        run: |
          mkdir -p ~/.m2
          cat << EOF > ~/.m2/settings.xml
          <settings>
            <servers>
              <server>
                <id>anypoint-exchange-v3</id>
                <username>~~~Client~~~</username>
                <password>${ANYPOINT_CLIENT_ID}~?~${ANYPOINT_CLIENT_SECRET}</password>
              </server>
            </servers>
          </settings>
          EOF
        env:
          ANYPOINT_CLIENT_ID: ${{ secrets.ANYPOINT_CLIENT_ID }}
          ANYPOINT_CLIENT_SECRET: ${{ secrets.ANYPOINT_CLIENT_SECRET }}
 
      - name: Deploy to CloudHub (QA)
        run: |
          mvn deploy -DmuleDeploy -Pqa -DskipTests \
            -Danypoint.client.id=${{ secrets.ANYPOINT_CLIENT_ID }} \
            -Danypoint.client.secret=${{ secrets.ANYPOINT_CLIENT_SECRET }} \
            -Danypoint.org.id=${{ secrets.ANYPOINT_ORG_ID }} \
            -Denv.DB_PASSWORD=${{ secrets.QA_DB_PASSWORD }} \
            -Denv.API_CLIENT_SECRET=${{ secrets.QA_API_CLIENT_SECRET }}
 
  # Deploy to Production
  deploy-prod:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: deploy-qa
    if: github.ref == 'refs/heads/main' || github.event.inputs.environment == 'prod'
    environment:
      name: production
      url: https://orders-api.cloudhub.io
 
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
 
      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          java-version: ${{ env.JAVA_VERSION }}
          distribution: 'temurin'
          cache: maven
 
      - name: Configure Maven Settings
        run: |
          mkdir -p ~/.m2
          cat << EOF > ~/.m2/settings.xml
          <settings>
            <servers>
              <server>
                <id>anypoint-exchange-v3</id>
                <username>~~~Client~~~</username>
                <password>${ANYPOINT_CLIENT_ID}~?~${ANYPOINT_CLIENT_SECRET}</password>
              </server>
            </servers>
          </settings>
          EOF
        env:
          ANYPOINT_CLIENT_ID: ${{ secrets.ANYPOINT_CLIENT_ID }}
          ANYPOINT_CLIENT_SECRET: ${{ secrets.ANYPOINT_CLIENT_SECRET }}
 
      - name: Deploy to CloudHub (Production)
        run: |
          mvn deploy -DmuleDeploy -Pprod -DskipTests \
            -Danypoint.client.id=${{ secrets.ANYPOINT_CLIENT_ID }} \
            -Danypoint.client.secret=${{ secrets.ANYPOINT_CLIENT_SECRET }} \
            -Danypoint.org.id=${{ secrets.ANYPOINT_ORG_ID }} \
            -Denv.DB_PASSWORD=${{ secrets.PROD_DB_PASSWORD }} \
            -Denv.API_CLIENT_SECRET=${{ secrets.PROD_API_CLIENT_SECRET }}
 
      - name: Verify Production Deployment
        run: |
          echo "Waiting for deployment to complete..."
          sleep 90
          curl -f https://orders-api.cloudhub.io/api/health || exit 1
 
      - name: Create Release Tag
        if: success()
        run: |
          VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
          git tag "v${VERSION}-$(date +%Y%m%d%H%M%S)"
          git push origin --tags

Runtime Fabric Deployment Workflow

# .github/workflows/mule-rtf-deploy.yml
name: Deploy to Runtime Fabric
 
on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'RTF Environment'
        required: true
        type: choice
        options:
          - rtf-dev
          - rtf-prod
      target:
        description: 'RTF Target Name'
        required: true
        default: 'rtf-cluster-1'
 
jobs:
  deploy-rtf:
    name: Deploy to RTF
    runs-on: ubuntu-latest
    environment: ${{ github.event.inputs.environment }}
 
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
 
      - name: Set up JDK 11
        uses: actions/setup-java@v4
        with:
          java-version: '11'
          distribution: 'temurin'
          cache: maven
 
      - name: Configure Maven Settings
        run: |
          mkdir -p ~/.m2
          cat << EOF > ~/.m2/settings.xml
          <settings>
            <servers>
              <server>
                <id>anypoint-exchange-v3</id>
                <username>~~~Client~~~</username>
                <password>${ANYPOINT_CLIENT_ID}~?~${ANYPOINT_CLIENT_SECRET}</password>
              </server>
            </servers>
          </settings>
          EOF
        env:
          ANYPOINT_CLIENT_ID: ${{ secrets.ANYPOINT_CLIENT_ID }}
          ANYPOINT_CLIENT_SECRET: ${{ secrets.ANYPOINT_CLIENT_SECRET }}
 
      - name: Build Application
        run: mvn clean package -DskipTests
 
      - name: Deploy to Runtime Fabric
        run: |
          mvn deploy -DmuleDeploy -Prtf -DskipTests \
            -Danypoint.client.id=${{ secrets.ANYPOINT_CLIENT_ID }} \
            -Danypoint.client.secret=${{ secrets.ANYPOINT_CLIENT_SECRET }} \
            -Danypoint.org.id=${{ secrets.ANYPOINT_ORG_ID }} \
            -Drtf.target=${{ github.event.inputs.target }} \
            -Drtf.environment=${{ github.event.inputs.environment }} \
            -Drtf.public.url=${{ secrets.RTF_PUBLIC_URL }}
 
      - name: Wait for Deployment
        run: |
          echo "Waiting for RTF deployment to stabilize..."
          sleep 120
 
      - name: Verify RTF Deployment
        run: |
          curl -f ${{ secrets.RTF_PUBLIC_URL }}/api/health || exit 1

Publish to Exchange Workflow

# .github/workflows/publish-exchange.yml
name: Publish to Anypoint Exchange
 
on:
  release:
    types: [published]
  workflow_dispatch:
 
jobs:
  publish:
    name: Publish to Exchange
    runs-on: ubuntu-latest
 
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
 
      - name: Set up JDK 11
        uses: actions/setup-java@v4
        with:
          java-version: '11'
          distribution: 'temurin'
          cache: maven
 
      - name: Configure Maven Settings
        run: |
          mkdir -p ~/.m2
          cat << EOF > ~/.m2/settings.xml
          <settings>
            <servers>
              <server>
                <id>anypoint-exchange-v3</id>
                <username>~~~Client~~~</username>
                <password>${ANYPOINT_CLIENT_ID}~?~${ANYPOINT_CLIENT_SECRET}</password>
              </server>
              <server>
                <id>exchange-private</id>
                <username>~~~Client~~~</username>
                <password>${ANYPOINT_CLIENT_ID}~?~${ANYPOINT_CLIENT_SECRET}</password>
              </server>
            </servers>
          </settings>
          EOF
        env:
          ANYPOINT_CLIENT_ID: ${{ secrets.ANYPOINT_CLIENT_ID }}
          ANYPOINT_CLIENT_SECRET: ${{ secrets.ANYPOINT_CLIENT_SECRET }}
 
      - name: Set Version from Tag
        if: github.event_name == 'release'
        run: |
          VERSION=${GITHUB_REF#refs/tags/v}
          mvn versions:set -DnewVersion=$VERSION
 
      - name: Publish to Exchange
        run: |
          mvn deploy -DskipTests \
            -DaltDeploymentRepository=exchange-private::default::https://maven.anypoint.mulesoft.com/api/v3/organizations/${{ secrets.ANYPOINT_ORG_ID }}/maven
        env:
          ANYPOINT_CLIENT_ID: ${{ secrets.ANYPOINT_CLIENT_ID }}
          ANYPOINT_CLIENT_SECRET: ${{ secrets.ANYPOINT_CLIENT_SECRET }}

MUnit Testing Configuration

Sample MUnit Test

<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:munit="http://www.mulesoft.org/schema/mule/munit"
      xmlns:munit-tools="http://www.mulesoft.org/schema/mule/munit-tools"
      xmlns="http://www.mulesoft.org/schema/mule/core"
      xmlns:http="http://www.mulesoft.org/schema/mule/http"
      xsi:schemaLocation="
        http://www.mulesoft.org/schema/mule/core
        http://www.mulesoft.org/schema/mule/core/current/mule.xsd
        http://www.mulesoft.org/schema/mule/munit
        http://www.mulesoft.org/schema/mule/munit/current/mule-munit.xsd
        http://www.mulesoft.org/schema/mule/munit-tools
        http://www.mulesoft.org/schema/mule/munit-tools/current/mule-munit-tools.xsd
        http://www.mulesoft.org/schema/mule/http
        http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd">
 
    <munit:config name="orders-api-test-suite.xml"/>
 
    <!-- Test: Create Order - Success -->
    <munit:test name="create-order-success-test"
                description="Test successful order creation"
                doc:id="test-1">
 
        <munit:behavior>
            <!-- Mock database insert -->
            <munit-tools:mock-when processor="db:insert" doc:name="Mock DB Insert">
                <munit-tools:with-attributes>
                    <munit-tools:with-attribute
                        attributeName="doc:name"
                        whereValue="Insert Order"/>
                </munit-tools:with-attributes>
                <munit-tools:then-return>
                    <munit-tools:payload value="#[1]"/>
                </munit-tools:then-return>
            </munit-tools:mock-when>
 
            <!-- Mock external API call -->
            <munit-tools:mock-when processor="http:request" doc:name="Mock HTTP Request">
                <munit-tools:with-attributes>
                    <munit-tools:with-attribute
                        attributeName="doc:name"
                        whereValue="Call Inventory Service"/>
                </munit-tools:with-attributes>
                <munit-tools:then-return>
                    <munit-tools:payload value="#[{available: true, quantity: 100}]"/>
                </munit-tools:then-return>
            </munit-tools:mock-when>
        </munit:behavior>
 
        <munit:execution>
            <set-payload value='#[{
                "customerId": "CUST-001",
                "items": [
                    {"productId": "PROD-001", "quantity": 2},
                    {"productId": "PROD-002", "quantity": 1}
                ]
            }]' mimeType="application/json"/>
 
            <flow-ref name="create-order-flow"/>
        </munit:execution>
 
        <munit:validation>
            <!-- Assert HTTP status -->
            <munit-tools:assert-that
                expression="#[attributes.statusCode]"
                is="#[MunitTools::equalTo(201)]"
                message="Expected 201 Created status"/>
 
            <!-- Assert response contains orderId -->
            <munit-tools:assert-that
                expression="#[payload.orderId]"
                is="#[MunitTools::notNullValue()]"
                message="Response should contain orderId"/>
 
            <!-- Assert response status -->
            <munit-tools:assert-that
                expression="#[payload.status]"
                is="#[MunitTools::equalTo('CREATED')]"
                message="Order status should be CREATED"/>
        </munit:validation>
 
    </munit:test>
 
    <!-- Test: Create Order - Validation Error -->
    <munit:test name="create-order-validation-error-test"
                description="Test order creation with invalid data"
                expectedErrorType="APP:VALIDATION_ERROR">
 
        <munit:execution>
            <!-- Missing required fields -->
            <set-payload value='#[{
                "items": []
            }]' mimeType="application/json"/>
 
            <flow-ref name="create-order-flow"/>
        </munit:execution>
 
    </munit:test>
 
    <!-- Test: Get Order - Found -->
    <munit:test name="get-order-found-test"
                description="Test retrieving existing order">
 
        <munit:behavior>
            <munit-tools:mock-when processor="db:select" doc:name="Mock DB Select">
                <munit-tools:then-return>
                    <munit-tools:payload value="#[[{
                        order_id: 'ORD-12345',
                        customer_id: 'CUST-001',
                        status: 'PROCESSING',
                        total: 150.00
                    }]]"/>
                </munit-tools:then-return>
            </munit-tools:mock-when>
        </munit:behavior>
 
        <munit:execution>
            <set-variable variableName="orderId" value="ORD-12345"/>
            <flow-ref name="get-order-flow"/>
        </munit:execution>
 
        <munit:validation>
            <munit-tools:assert-that
                expression="#[attributes.statusCode]"
                is="#[MunitTools::equalTo(200)]"/>
 
            <munit-tools:assert-that
                expression="#[payload.orderId]"
                is="#[MunitTools::equalTo('ORD-12345')]"/>
        </munit:validation>
 
    </munit:test>
 
    <!-- Test: Get Order - Not Found -->
    <munit:test name="get-order-not-found-test"
                description="Test retrieving non-existent order"
                expectedErrorType="APP:NOT_FOUND">
 
        <munit:behavior>
            <munit-tools:mock-when processor="db:select">
                <munit-tools:then-return>
                    <munit-tools:payload value="#[[]]"/>
                </munit-tools:then-return>
            </munit-tools:mock-when>
        </munit:behavior>
 
        <munit:execution>
            <set-variable variableName="orderId" value="NON-EXISTENT"/>
            <flow-ref name="get-order-flow"/>
        </munit:execution>
 
    </munit:test>
 
</mule>

GitHub Secrets Configuration

Required Secrets

# GitHub Repository Secrets Configuration
 
# Anypoint Platform Credentials
ANYPOINT_CLIENT_ID: "connected-app-client-id"
ANYPOINT_CLIENT_SECRET: "connected-app-client-secret"
ANYPOINT_ORG_ID: "org-business-group-id"
 
# MuleSoft Nexus (for EE dependencies)
MULESOFT_NEXUS_USERNAME: "nexus-username"
MULESOFT_NEXUS_PASSWORD: "nexus-password"
 
# Environment-specific secrets
DEV_DB_PASSWORD: "dev-database-password"
DEV_API_CLIENT_SECRET: "dev-api-secret"
 
QA_DB_PASSWORD: "qa-database-password"
QA_API_CLIENT_SECRET: "qa-api-secret"
 
PROD_DB_PASSWORD: "prod-database-password"
PROD_API_CLIENT_SECRET: "prod-api-secret"
 
# Runtime Fabric (if using RTF)
RTF_PUBLIC_URL: "https://api.yourdomain.com"

Best Practices Summary

cicd_best_practices:
  pipeline_design:
    - "Use branch-based deployment (develop → QA, main → Prod)"
    - "Implement environment approvals for production"
    - "Cache Maven dependencies to speed up builds"
    - "Run tests in parallel when possible"
    - "Fail fast on test failures"
 
  security:
    - "Store all credentials in GitHub Secrets"
    - "Use Connected Apps instead of user credentials"
    - "Run OWASP dependency checks"
    - "Scan for secrets in code"
    - "Use environment-specific secrets"
 
  testing:
    - "Enforce minimum 80% code coverage"
    - "Mock external dependencies in tests"
    - "Run integration tests in CI"
    - "Upload test reports as artifacts"
 
  deployment:
    - "Use Maven profiles for environment configuration"
    - "Implement health checks post-deployment"
    - "Tag releases after successful production deployment"
    - "Use blue-green or rolling deployments for zero downtime"
 
  monitoring:
    - "Configure deployment notifications (Slack, Teams)"
    - "Set up deployment tracking in monitoring tools"
    - "Log deployment metadata for traceability"
    - "Monitor deployment success/failure rates"

Conclusion

Automating MuleSoft CI/CD with Maven and GitHub Actions streamlines deployments across CloudHub and Runtime Fabric. Key practices include using Connected Apps for authentication, environment-specific profiles, comprehensive MUnit testing, and security scanning. This pipeline ensures reliable, repeatable deployments with proper governance.

Weekly AI Security & Automation Digest

Get the latest on AI Security, workflow automation, secure integrations, and custom platform development delivered weekly.

No spam. Unsubscribe anytime.