Back to Blog
ProductDock: Danijel Dragičević

5 minutes read

Automating Mule deployments: From Dev to Prod with GitHub Actions

Danijel Dragičević

MuleSoft Developer

In this blog post, we will explore the automation of deployment of Mule applications to both CloudHub 1.0 and CloudHub 2.0 using GitHub Actions. We’ll walk through how to configure our Mule application for deployment on both versions of CloudHub, streamlining the process from build to deployment.

Throughout this guide, we’ll demonstrate how to deploy to different environments, which will be particularly helpful for teams transitioning between these two services. CloudHub 2.0 offers improved scalability, flexibility, and resource consumption. Also, in the future, it will be the main focus of MuleSoft when it comes to updates and security patches.

Automating Mule deployments: From Dev to Prod with GitHub Actions: CI/CD pipeline architecture
CI/CD pipeline architecture

Configuring environments on the Anypoint Platform

To begin, set up the required environments on the Anypoint Platform. We can do this either through the platform’s UI’s Access Management section or the Anypoint CLI. While both methods are effective, the CLI allows for automation, faster execution, and greater scalability when managing multiple environments. The instructions for installing the Anypoint CLI are in the following documentation: Anypoint CLI Installation.

Once the Anypoint CLI is installed, authenticate using your platform credentials and create the necessary environments. If we’re on a free account, all environments must be of type “Sandbox,” while “Production” environments require a paid subscription.

# Authenticate:
$ anypoint-cli-v4 conf username <username>
$ anypoint-cli-v4 conf password <password>
$ anypoint-cli-v4 conf organization <organization>

# Create environments:
$ anypoint-cli-v4 account:environment:create dev --type sandbox
$ anypoint-cli-v4 account:environment:create qa --type sandbox
$ anypoint-cli-v4 account:environment:create prod --type sandbox

Preparing the Mule application for deployment

With our environments set up, the next step is to configure the Mule application. In this example, we’re using a basic application with a /ping endpoint, which returns key identifiers such as groupId, artifactId, and version.

The API contract is defined in a RAML file, a widely used specification in the MuleSoft ecosystem. The OpenAPI Specification is also supported and can be utilized if needed.

#%RAML 1.0
title: mule-github-actions-api
version: v1.0.0

/ping:
  get:
    displayName: Get application info
    description: Shows application identifiers, fetched from the pom file
    responses:
      200:
        body:
          application/json:
            type: !include dataTypes/ping-response-data-type.raml
            example: !include examples/ping-response-example.raml

We configure pom.xml and property placeholders to populate these values dynamically during the build process.

<resources>
    <resource>
        <directory>src/main/resources</directory>
        <filtering>false</filtering>
    </resource>
    <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
        <includes>
            <include>config/common.yaml</include>
        </includes>
    </resource>
</resources>

This configuration instructs Maven to substitute values defined in the config/common.yaml with the values from the pom.xml.

Values in the config file can be accessed with the DataWeave using the p(propertyName: String) function:

%dw 2.0
output application/json skipNullOn="everywhere"
---
{
    groupId: p('application.groupId'),
    artifactId: p('application.artifactId'),
    version: p('application.version')
}

For deploying to CloudHub 1.0 and CloudHub 2.0, the mule-maven-plugin is essential. We configure separate profiles in pom.xml to handle deployments to either CloudHub version. These profiles allow us to switch between deployment environments by specifying the relevant profile in the GitHub Action script.

Below is the plugin configuration for CloudHub 1.0 deployments:

<profiles>
  <profile>
      <id>cloudHubDeployment</id>
      <build>
          <plugins>
              <plugin>
                  <groupId>org.mule.tools.maven</groupId>
                  <artifactId>mule-maven-plugin</artifactId>
                  <version>${mule.maven.plugin.version}</version>
                  <extensions>true</extensions>
                  <configuration>
                      <cloudHubDeployment>
                          <uri>https://anypoint.mulesoft.com</uri>
                          <muleVersion>${app.runtime}</muleVersion>
                          <username>${anypoint.username}</username>
                          <password>${anypoint.password}</password>
                          <applicationName>
${project.artifactId}-${anypoint.environment}
</applicationName>
                          <environment>${anypoint.environment}</environment>
                          <releaseChannel>EDGE</releaseChannel>
                          <javaVersion>17</javaVersion>
                          <workerType>MICRO</workerType>
                          <region>us-east-2</region>
                          <workers>1</workers>
                          <objectStoreV2>true</objectStoreV2>
                      </cloudHubDeployment>
                      <classifier>mule-application</classifier>
                  </configuration>
              </plugin>
          </plugins>
      </build>
  </profile>
</profiles>


And here is the configuration for CloudHub 2.0 deployments:

<profiles>
    <profile>
        <id>cloudhub2Deployment</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.mule.tools.maven</groupId>
                    <artifactId>mule-maven-plugin</artifactId>
                    <version>${mule.maven.plugin.version}</version>
                    <extensions>true</extensions>
                    <configuration>
                        <cloudhub2Deployment>
                            <uri>https://anypoint.mulesoft.com</uri>
                            <muleVersion>${app.runtime}</muleVersion>
                            <username>${anypoint.username}</username>
                            <password>${anypoint.password}</password>
                            <applicationName>
${project.artifactId}-${anypoint.environment}
  </applicationName>
                            <environment>${anypoint.environment}</environment>
                            <releaseChannel>EDGE</releaseChannel>
                            <javaVersion>17</javaVersion>
                            <replicas>1</replicas>
                            <target>Cloudhub-US-East-2</target>
                            <vCores>0.1</vCores>
                            <provider>MC</provider>
                            <deploymentSettings>
                                <generateDefaultPublicUrl>true</generateDefaultPublicUrl>
                            </deploymentSettings>
                        </cloudhub2Deployment>
                        <classifier>mule-application</classifier>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

The configuration varies slightly between CloudHub 1.0 and 2.0, especially in terms of region, worker types, and other resource allocations. Refer to the MuleSoft Deployment Documentation for further details on each parameter.

Versioning is critical when deploying to CloudHub 2.0, as each deployment creates a new asset in Exchange. To ensure the application is ready for release, use the versions-maven-plugin to remove SNAPSHOT from the version. After deployment to production, increment the version for the next development cycle.

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>versions-maven-plugin</artifactId>
  <version>2.8.1</version>
</plugin>

Setting up a GitHub repository

Next, we have to create a GitHub repository to store our source code and automate the deployment process. We can use the GitHub CLI to create and manage the repository or use the GitHub web interface for a more visual experience.

Once this tool is installed, we can authenticate and verify our GitHub account using:

# Authenticate:
$ gh auth login

# Verify:
$ gh auth status
github.com
  ✓ Logged in to github.com account danijeldragicevic (keyring)
  - Active account: true
  - Git operations protocol: https
  - Token: gho_************************************
  - Token scopes: 'gist', 'read:org', 'repo', 'workflow'

Ensure that our authentication token includes the repo scope, as we’ll need this token to interact with our repository through GitHub Actions. 

To create a new repository for our application, run:

$ gh repo create mule-github-actions-app --public
✓ Created repository danijeldragicevic/mule-github-actions-app on GitHub
  https://github.com/danijeldragicevic/mule-github-actions-app

It’s important to note that our repository must be public for free GitHub accounts to use GitHub Actions.

Set up a develop branch and protect it by requiring pull requests for all changes. This approach ensures that code merges into the branch only after review and approval.

$ cd ~/AnypointCodeBuilder/workspace/mule-github-actions-app
$ git init
$ git add .
$ git commit -m "first commit"
$ git remote add origin https://github.com/danijeldragicevic/mule-github-actions-app.git
$ git branch -M develop
$ git push -u origin develop

Create branch protection rule:

curl -X PUT \
-u danijeldragicevic:gToken \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/danijeldragicevic/mule-github-actions-app/branches/develop/protection \
-d '{
  "required_pull_request_reviews": {
    "required_approving_review_count": 1
  },
  "restrictions": {
    "users": ["danijeldragicevic"]
  },
  "enforce_admins": true,
  "required_status_checks": null
}'

This setup ensures that code can only be merged into this branch with at least one approval from a team member. We can adjust the number of required approvals based on the size of the team. The restrictions also apply to administrators, enforcing that all changes must go through pull requests rather than direct commits.

Finally, we have to create secrets in our GitHub repository for authentication against the Anypoint Platform and GitHub itself. GitHub Actions will use these secrets to securely handle authentication and authorization during the deployment process.

$ gh secret set ANYPOINT_PLATFORM_USERNAME -b"username" --repo danijeldragicevic/mule-github-actions-app
$ gh secret set ANYPOINT_PLATFORM_PASSWORD -b"password" --repo danijeldragicevic/mule-github-actions-app
$ gh secret set G_TOKEN -b"gToken" --repo danijeldragicevic/mule-github-actions-app

Automating deployments with GitHub Actions

With the repository and secrets configured, it’s time to set up GitHub Actions. First, we have to create a .github/workflows/main.yaml file at the root level of our project, which is a default location where GitHub looks for workflow definitions. When changes are pushed to the repository, GitHub checks this directory for .yaml files that define workflows to trigger actions. Our example uses the workflow_dispatch trigger, which allows for manual deployments to specific environments (dev, qa, prod).

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment to deploy to'
        type: choice
        required: true
        options:
          - dev
          - qa
          - prod

Permissions are granted for GitHub Actions to commit to the repository when needed.

permissions:
  contents: write

The workflow includes build, deploy, and verify jobs. The build job has an additional step to create a release if deployment is intended for the QA environment: removing SNAPSHOT from the application version and publishing the release.

- name: Create release
  if: ${{ github.event.inputs.environment == 'qa' }}
  run: |
    mvn versions:set -DremoveSnapshot versions:commit
    version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
     
    git config --global user.email "github-actions[bot]@users.noreply.github.com"
    git config --global user.name "github-actions[bot]"

    git add pom.xml
    git commit -m "Create release $version"
    git push https://x-access-token:$githubToken@github.com/${{ github.repository }}.git develop

    git tag $version
    git push https://x-access-token:$githubToken@github.com/${{ github.repository }}.git $version
  shell: bash
  env:
    githubToken: ${{ secrets.G_TOKEN }}

Although including a test job is common, Mule applications require a commercial license to access and download the MUnit library. While MUnit tests can be run locally (as the libs are included with Anypoint Studio or Anypoint Code Builder installation for free), this isn’t possible on a bare Linux instance, such as those used in GitHub Actions, without configuring the access to the Mule Soft’s Nexus repository.

The deploy job sets the deployment type and directs the deployment to CloudHub 1.0 or CloudHub 2.0, depending on the selected environment.

- name: Set deployment type
  uses: ./.github/actions/deployment-type-config
  with:
    targetEnvironment: ${{ github.event.inputs.environment }}

- name: Deploy to CH1.0
  if: env.deploymentType == 'cloudHubDeployment'
  uses: ./.github/actions/deploy-to-ch1-config
  with:
    username: ${{ secrets.ANYPOINT_PLATFORM_USERNAME }}
    password: ${{ secrets.ANYPOINT_PLATFORM_PASSWORD }}
    targetEnvironment: ${{ github.event.inputs.environment }}

- name: Deploy to CH2.0
  if: env.deploymentType == 'cloudhub2Deployment'
  uses: ./.github/actions/deploy-to-ch2-config
  with:
    username: ${{ secrets.ANYPOINT_PLATFORM_USERNAME }}
    password: ${{ secrets.ANYPOINT_PLATFORM_PASSWORD }}
    targetEnvironment: ${{ github.event.inputs.environment }}

Our action script that deploys the application has two steps. The first step is to publish the built application to Anypoint Exchange, MuleSoft’s central repository for sharing and discovering reusable assets such as APIs and connectors.

- name: Publish to Exchange
  run: |
    mvn deploy --settings .maven/settings.xml -DskipMunitTests \
     -Danypoint.username="$username" \
     -Danypoint.password="$password" \
  shell: bash
  env:
    username: ${{ inputs.username }}
    password: ${{ inputs.password }}

Once the application is published to Exchange, the next step is to deploy it to the target environment (in this case, CloudHub 2.0):

- name: Deploy the application
  run: |
    artifactName=$(ls *.jar | head -1)
    mvn deploy --settings .maven/settings.xml -DskipMunitTests -DmuleDeploy -Pcloudhub2Deployment\
     -Dmule.artifact=$artifactName \
     -Danypoint.username="$username" \
     -Danypoint.password="$password" \
     -Danypoint.environment="$targetEnvironment"
  shell: bash
  env:
    username: ${{ inputs.username }}
    password: ${{ inputs.password }}
    targetEnvironment: ${{ inputs.targetEnvironment }}

Verifying deployments and preparing for the next cycle

After deployment, the verify job ensures the service is running and responding by pinging the appropriate instance of the application.

- name: Verify deployment
  run: |
    if [[ "$targetEnvironment" == "dev" ]]; then
      curl -f http://mule-github-actions-app-dev.us-e2.cloudhub.io/api/ping || exit 1
    
    elif [[ "$targetEnvironment" == "qa" ]]; then
      curl -f http://mule-github-actions-app-qa.us-e2.cloudhub.io/api/ping || exit 1
    
    elif [[ "$targetEnvironment" == "prod" ]]; then
      curl -f https://mule-github-actions-app-prod-qefjd6.5sc6y6-3.usa-e2.cloudhub.io/api/ping || exit 1
    
    else
      echo "Skipping deployment verification"
      exit 0
    fi
  shell: bash
  env:
    targetEnvironment: ${{ github.event.inputs.environment }}

If successful, the application version is incremented, and the branch is prepared for the next development cycle, ensuring that the project is ready for further work.

- name: Set next development cycle
  if: ${{ github.event.inputs.environment == 'prod' }}
  run: |
    mvn versions:set -DnextSnapshot versions:commit
    version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
    
    git config --global user.email "github-actions[bot]@users.noreply.github.com"
    git config --global user.name "github-actions[bot]"
    
    git checkout develop || git checkout -b develop
    git add pom.xml
    git commit -m "Increase version to $version for next development cycle"
    git push https://x-access-token:$githubToken@github.com/${{ github.repository }}.git develop
  shell: bash
  env:
    githubToken: ${{ secrets.G_TOKEN }}

Conclusion

By integrating GitHub Actions with the Anypoint Platform, we can automate the entire deployment lifecycle of Mule applications — from building and testing to deploying and verifying. This approach reduces manual intervention, improves reliability, and ensures a consistent deployment process across all environments. As we continue to scale our deployments, leveraging tools like GitHub Actions and Anypoint CLI will become even more valuable, saving time and reducing errors.

To see the complete source code and this deployment process in action, visit the GitHub repository. Feel free to explore, fork, or contribute to the project!

ProductDock: Danijel Dragičević

Danijel Dragičević

MuleSoft Developer

Danijel is a MuleSoft developer, content creator, and mentor. He has been part of our family since April 2014. Over the past few years, he has focused on developing integrations using Java and MuleSoft’s Anypoint Platform, following the API-led connectivity strategy.


Related posts.