# Build BEAT IT Store from scratch: GitHub or Azure Repos → Maven → Jenkins → CloudHub 2.0

This original walkthrough builds a small, synthetic Mule application and a reviewable delivery path. It includes the actual parent POM, child POM, HTTP flows, three MUnit tests, Jenkinsfile, settings template, client requests and offline checks. Start with the files in this folder; do not paste a proprietary training project into them.

**What has actually been checked:** the supplied XML/JSON structure and twelve local Python tests using synthetic bytes, stubbed HTTP and a stubbed Maven process. **What has not been executed:** Maven dependency resolution, Mule compilation, the three MUnit tests, Jenkins, Exchange publication, Postman requests or CloudHub deployment. Expected outputs below are acceptance criteria for your configured environment, not recorded live results.

Download the [starter ZIP](/resources/mulesoft/beatit-from-scratch-cloudhub.zip), extract it into a new practice folder and open a terminal in its from-scratch-cloudhub directory, where pom.xml and Jenkinsfile are present. Keep the extracted folder intact; do not initialize Git in your Downloads folder or home directory.

There are two modes. Offline inspection and Python checks need no accounts and make no network requests. Publishing and deployment steps are instructions for an authorized operator: they really contact services, can update applications and can consume paid CloudHub capacity. Creating this guide has not purchased capacity or deployed anything.

## 1. Understand the project and choose the target

The demonstration has GET /health, GET /orders/O100 and GET /orders/UNKNOWN. O100 is a hard-coded synthetic order with SKU "0012" and quantity 2. It does not read a database, send messages or fulfill an order. It deliberately has no production identity policy. Do not expose this starter as a production customer API without adding the required security and business behavior.

This guide targets **CloudHub 2.0**, using the cloudhub2Deployment configuration, an approved shared/private-space target, replicas and the chosen capacity. CloudHub 1 uses a different deployment configuration and operational model. Do not rename the XML element or copy a CloudHub 1 worker/region example into this project.

The delivery path is:

1. Commit reviewed source to one canonical Git repository.
2. Resolve/install the parent for local work; publish a changed parent release separately.
3. Build, run MUnit and publish one application release to Exchange in one Maven lifecycle.
4. Record its exact package digest and reviewed control-file hashes.
5. Compare the published Exchange package with the local tested package.
6. Deploy that existing candidate to sandbox, inspect actual results and approve promotion separately.
7. Promote the same bytes/coordinates with reviewed environment configuration; stop on mismatch.

A release identifier is not proof of identical bytes. The scripts check local hashes and attempt an authenticated Exchange byte comparison. That mechanism itself still needs authorized end-to-end verification against your repository before claiming live same-artifact promotion.

## 2. Prepare your workstation

The command examples use **Bash on Linux**. macOS can use an appropriate Bash environment with its own Java paths. On Windows, use an approved WSL/Linux build environment, or translate commands deliberately to PowerShell. Do not paste Linux sudo, export or shell quoting into Windows Command Prompt. Jenkins below uses a Linux agent and sh steps.

Install Git, Python 3.10 or newer, a supported JDK 17 for Mule builds and an appropriate editor. Install Anypoint Studio or Anypoint Code Builder separately under your authorized MuleSoft setup if you want graphical flow editing. The Python checks do not install Mule.

On a new approved Ubuntu 24.04 workstation, install the basic packages first. On managed machines use the approved administrator process instead:

```bash
sudo apt-get update
sudo apt-get install git python3 curl ca-certificates openjdk-17-jdk
```

Expected: packages install without unresolved apt errors. This does not install Maven, Studio or a Mule runtime. Check the tools:

```bash
git --version
python3 --version
java -version
mvn -version
```

Expected: Git and Python report versions; the Maven process reports its own Java home. A Java executable on PATH does not prove Maven uses that same JDK.

The supplied pins were checked against current public release documentation on 26 October 2026: Mule Maven Plugin 4.10.3, MUnit 3.7.5 and HTTP Connector 1.12.1. MMP 4.10.3 and MUnit 3.7.5 document Maven 3.9.0–3.9.15 support; this project chooses Java 17. Select a supported current Mule runtime available in your organization, compatible with the 4.9.0 minimum in mule-artifact.json and Java 17. Do not copy an obsolete plugin version from a documentation example or suppress Java compatibility validation.

For a reproducible example, Apache publishes Maven 3.9.14, within the documented plugin compatibility range. Before using this historical pin, review current Apache security/release notices and your organization policy; choose a newer compatible reviewed patch if required. These are actual installation commands for that pin on the approved Linux host:

```bash
set -e
mkdir -p .private/tool-downloads
cd .private/tool-downloads
curl -fSLO https://archive.apache.org/dist/maven/maven-3/3.9.14/binaries/apache-maven-3.9.14-bin.tar.gz
curl -fSLO https://archive.apache.org/dist/maven/maven-3/3.9.14/binaries/apache-maven-3.9.14-bin.tar.gz.sha512
python3 -c "import hashlib,pathlib; p=pathlib.Path('apache-maven-3.9.14-bin.tar.gz'); expected=pathlib.Path(str(p)+'.sha512').read_text().split()[0]; assert hashlib.sha512(p.read_bytes()).hexdigest()==expected, 'Checksum mismatch'; print('SHA-512 matches')"
sudo tar -xzf apache-maven-3.9.14-bin.tar.gz -C /opt
export PATH=/opt/apache-maven-3.9.14/bin:$PATH
mvn -version
cd ../..
```

Only extract after checksum success. Use Apache's signature-verification procedure as required by policy; stop on a mismatch. Expected: Maven reports 3.9.14 and the intended Java 17 home. Set the same approved Maven/JDK paths in your shell profile or Jenkins tools later. A distribution's default apt Maven may be older than the plugins support. Fix the selected tool version instead of disabling tests. See [Apache release notes](https://maven.apache.org/docs/3.9.14/release-notes.html) and the [official binary archive](https://archive.apache.org/dist/maven/maven-3/3.9.14/binaries/).

## 3. Create GitHub and Azure Repos correctly

Git is the local version-control tool. GitHub and Azure Repos are remote hosting services. You can use either for the canonical repository; you do not need two independently editable copies.

### Option A: GitHub is canonical

1. Sign in to your authorized GitHub account/organization.
2. Choose New repository. Name it beatit-store-integration, choose the approved visibility and create an **empty** repository. Do not initialize a competing README or history for these existing starter files.
3. In the repository's Code menu, copy its approved HTTPS or SSH clone URL.
4. Configure authentication through your organization's Git credential manager, approved GitHub App or suitably scoped credential. Do not embed a token in a remote URL.
5. Follow the organization's approved empty-repository bootstrap process for the initial main commit below. Immediately after that first authorized push, configure main protection/rulesets before collaboration or credentialed Jenkins builds: required reviews/checks and restricted writes. If policy already blocks the bootstrap, ask the repository owner to perform the authorized bootstrap; do not bypass protection. Availability depends on account plan and permissions.

In the extracted starter folder:

```bash
git init
git branch -M main
git config user.name "YOUR_REVIEWED_DISPLAY_NAME"
git config user.email "YOUR_APPROVED_COMMIT_EMAIL"
git add .
git status --short
git diff --cached --stat
git commit -m "Add synthetic Store Mule starter and delivery templates"
git remote add origin https://github.com/REPLACE_OWNER/beatit-store-integration.git
git remote -v
git push -u origin main
```

Before committing, inspect settings/environment files and confirm that no credential values or generated targets were staged. The supplied settings.template.xml contains environment references only. Expected remote output is a successful main push and upstream tracking; exact progress text varies. If origin already exists, inspect it and use git remote set-url only after confirming the intended destination.

### Option B: Azure Repos is canonical

1. Open your authorized Azure DevOps organization and choose/create the approved project. A new organization or paid service is not required by this guide.
2. Open Repos → Files. Use the repository selector → New repository, choose Git and name it beatit-store-integration.
3. Keep it empty for the existing starter history. Select Clone and copy the HTTPS or SSH URL.
4. Use Git Credential Manager with Microsoft Entra authentication when available. An organization-scoped, expiring PAT is an alternative only under your policy; it cannot grant more permission than its owner. Do not use a token-bearing URL.
5. Under Project settings → Repositories, review repository access. Clear any option to add a README when creating this empty repository. After the authorized initial main push, configure Repos → Branches → main policies such as required reviewers/build validation before collaboration or credentialed Jenkins runs. Follow an existing organization bootstrap policy rather than bypassing it.

Use the same local initialization/commit steps above, but set origin to the actual copied URL:

```bash
git remote add origin https://dev.azure.com/REPLACE_ORG/REPLACE_PROJECT/_git/beatit-store-integration
git remote -v
git push -u origin main
```

Expected: the same commit appears under Azure Repos main. If authentication fails, investigate credential-manager identity, project membership and Code permission before creating broader tokens. Do not type an account password or PAT into source files.

### Optional controlled second remote

If governance requires both services, designate one as canonical. Create the other repository empty and make it a controlled read-only mirror for normal users. Add it under a different name:

```bash
git remote add mirror https://REPLACE_APPROVED_SECOND_REPOSITORY_URL
git push mirror main
```

This pushes main; it is not a full automatic mirror. Establish an authorized synchronization owner/job and explicit branch/tag rules if you need more. Do not use force-push or git push --mirror against a populated repository casually: those can overwrite/delete remote refs. Review the same canonical commit in both destinations; configure Jenkins from canonical main only. Neither remote should become a way to bypass review in the other.

Expected checkpoint: git remote -v shows the intended credential-free URLs; the canonical remote contains the reviewed initial commit. No cloud deployment has happened.

## 4. Inspect the parent and child files before editing

The root folder is both an aggregator and parent. Its pom.xml lists store-api as a module. The child lives in store-api/pom.xml and references ../pom.xml.

| File/element | Purpose | What it does not prove |
| --- | --- | --- |
| Parent packaging pom | Build configuration artifact | A deployable Mule application |
| Parent modules/store-api | Include child in root reactor | Inheritance by unrelated projects |
| Child parent GAV/relativePath | Inherit exact parent configuration | That the parent exists remotely |
| dependencyManagement | Supply dependency version defaults | Add those dependencies automatically |
| pluginManagement | Supply plugin defaults/executions | Activate every managed plugin automatically |
| Child build/plugins | Activate Mule extension and MUnit plugin | That any command has run |
| Child packaging mule-application | Mule application packaging | A successful deployment |
| distributionManagement | Publishing repository | Download/parent bootstrap access by itself |

The child explicitly declares HTTP, munit-runner and munit-tools dependencies. Their versions come from dependencyManagement. It explicitly declares the Mule Maven Plugin with extensions true and the MUnit Maven Plugin; that activates the managed defaults. The parent does not run MUnit on its own just because it manages the plugin.

The parent and application have independent release identities, initially both 1.0.0 for clarity. If only application logic changes, increment the child version. If managed build behavior changes, release a new parent version and update the child's parent reference. Do not silently mutate a published release version.

Open BOTH POMs and replace every literal REPLACE_WITH_BUSINESS_GROUP_UUID with the UUID of the business group that will own these Exchange assets. Exchange groupId is a literal organization/business-group ID; do not replace it with a Maven environment expression. Update the matching repository URL and deployment businessGroupId consistently. For another supported control plane, use the exact official regional repository/control-plane URLs and review script support; this starter's remote comparison supports US/EU hosts.

In root pom.xml replace REPLACE_WITH_EXACT_SUPPORTED_RUNTIME with the exact plain runtime version selected for MUnit. CloudHub's runtime image/channel selection is configured separately. Keep the artifact's minimum and Java compatibility coherent.

Run the offline checks:

```bash
python3 scripts/verify_setup.py
python3 scripts/test_setup.py
```

Expected: a structural success message and twelve Python tests pass. These tests parse files, inspect activation and exercise hash/guard behavior with dummy bytes and stubbed runners. They do not compile Mule or run MUnit. Do not rename their result “Mule tests passed.”

The --configured check also requires real release approver IDs in Jenkinsfile, so finish that replacement before enabling the protected pipeline:

```bash
python3 scripts/verify_setup.py --configured
```

Expected on an unedited download: a deliberate placeholder error. Expected after complete editing: structural success. Never bypass it by deleting checks just to get a green log.

## 5. Configure Anypoint access and environment values

Ask the organization's authorized administrator for the existing business group, sandbox environment, CloudHub 2 target/space, approved capacity and permitted runtime image. Do not create paid capacity simply to satisfy this exercise. Record the UUID and environment/target names exactly as shown in the tenant. The provided deployment profile is inactive until selected.

For CI/CD, create connected apps that act on their own behalf:

1. Open Anypoint Platform → Access Management → Connected Apps in the intended business group.
2. Choose Create App and the app-acts-on-its-own-behalf/client-credentials type.
3. Add the required scopes, selecting the intended business group/environment when prompted.
4. Review the Scopes Documentation tab for the actual permission methods in this tenant.
5. Save and store the generated client ID/secret in the organization's approved secret facility. Do not put their values into Git, a command's -D arguments, a screenshot or this guide.

Use separate identities where practical:

| Jenkins credential ID | Purpose | Permission boundary to review |
| --- | --- | --- |
| scm-read | Read canonical repository | Only the selected repository; no deployment permission |
| exchange-publisher | Publish reviewed parent/app releases | Exchange Contributor and Viewer for the owning business group |
| exchange-reader | Resolve/read published assets | Exchange Viewer for the required business group |
| ch2-sandbox-deployer | Deploy to reviewed sandbox | Target business group and sandbox environment |
| ch2-production-deployer | Separately authorized production deployment | Target business group and production environment |

The current Mule Maven CloudHub 2 guide explicitly calls out Design Center Developer for connected-app deployment authentication. CloudHub 2 permission documentation also specifies Exchange Contributor/Viewer for deployment from Exchange and Create Applications for Runtime Manager deployment; Read Applications and relevant network visibility support inspection. Have the administrator map these documented permissions to the app's available tenant scopes and grant only the target scope required. Reuse of a read-only Exchange identity inside a deployment tool does not remove the deployer's own required privileges. Do not grant Organization Administrator as a shortcut. Update/redeploy and private-space access can require additional tenant-specific reviewed permissions; inspect the scopes documentation instead of inventing an all-access role.

The settings.template.xml server ID exchange-v3 matches every Exchange repository ID. Its username marker and password composition are the documented connected-app Maven authentication format. The actual ID/secret come from environment variables; they are not literal values in that file.

Prepare non-secret deployment configuration:

```bash
cp environments/sandbox.example.json environments/sandbox.json
cp environments/production.example.json environments/production.json
```

Edit each JSON with the approved control-plane URI, environment, target, globally appropriate application name, exact runtime image/version, supported channel, replica count and vCore size. Match Java 17. Do not put secrets into either JSON. These are reviewed deployment inputs, not arbitrary request parameters.

The CH2 profile intentionally manages a complete property set containing http.port and app.name. When properties or secureProperties are supplied, CloudHub 2 redeployment uses the supplied set rather than merging everything from Runtime Manager. Inventory existing values before deploying over an existing app. Extend the complete managed set deliberately if the real application needs more; never assume an omitted secret/property will be preserved. This starter app uses no business secret. Do not include resolved secrets in a packaged properties file.

## 6. Install/publish the parent, then develop the child

These are three different operations:

- install puts the parent in your workstation's local Maven repository.
- Publishing the parent makes its POM available to fresh agents through Exchange as a custom asset.
- Publishing store-api creates the application's own release asset, not another copy of the parent.

For local parent resolution, from the root:

```bash
mvn -N install
```

The -N option means non-recursive: only the parent POM is installed. Expected: BUILD SUCCESS and a parent POM in the configured local Maven repository. This does not build the child, run its tests, upload Exchange assets or deploy CloudHub.

For authorized remote operations, bind EXCHANGE_CLIENT_ID and EXCHANGE_CLIENT_SECRET using your secure session/CI mechanism. A local Bash session can read them without echoing the secret or putting its value in shell history:

```bash
read -r -p "Exchange client ID: " EXCHANGE_CLIENT_ID
read -r -s -p "Exchange client secret: " EXCHANGE_CLIENT_SECRET
export EXCHANGE_CLIENT_ID EXCHANGE_CLIENT_SECRET
mkdir -p .private
cp settings.template.xml .private/settings.xml
chmod 600 .private/settings.xml
```

Do not run printenv, Maven -X, help:effective-settings or commands that display resolved credential settings. The file still contains references, but protect its directory and never archive resolved secret material.

Publish the configured parent version once, under authorization:

```bash
mvn -B -N -s .private/settings.xml -Ppublish-parent-exchange deploy
```

Expected: the parent pre-deploy and deploy goals complete and the parent custom asset version is available. The profile's exchange-mule-maven-plugin is inherited=false so its publication lifecycle does not contaminate the child Mule application. It is not a CloudHub deployment. If the release already exists, do not overwrite it: verify the existing parent or release a new parent version.

A fresh child-only environment needs a way to locate that parent before inheritance can be read. The child therefore includes its own bootstrap Exchange repository. To test remote resolution rather than a warm local cache, create a disposable child-POM folder under .private, copy store-api/pom.xml there and use an isolated local repository:

```bash
mkdir -p .private/parent-resolution
cp store-api/pom.xml .private/parent-resolution/pom.xml
mvn -B -s .private/settings.xml -f .private/parent-resolution/pom.xml -Dmaven.repo.local=.private/fresh-m2 help:effective-pom
```

Expected: parent resolution succeeds using the published exact GAV and the configured repository. This is an authorized network operation. It is not a Mule test. A 401/403 points to authentication/scope/repository ownership; “non-resolvable parent” can also mean wrong GAV or unpublished parent. Do not fix it by replacing the parent with an unrelated tutorial POM.

### Import into Studio or Code Builder

1. In Studio, choose File → Import. Use the existing Mule project import wizard available in your Studio version; if using Existing Maven Projects, select the root and include the store-api child. In Code Builder, open/import the existing project according to its current Mule project workflow.
2. Select the configured child folder, not a newly generated competing project that overwrites these files.
3. Confirm the workspace detects store-api as a Mule application, the HTTP connector resolves and the selected runtime/Java match the project.
4. Open src/main/mule/store-api.xml. You should see health-flow and order-lookup-flow. Check application.properties and the two GET listener methods.
5. Open src/test/munit/store-api-test.xml. The three tests cover health, preserved string SKU and unknown-order 404. They flow-reference the implementation with synthetic attributes; they do not contact inventory or shipping.
6. If Studio reports missing runtime, configure its actual installed runtime. If Maven dependencies fail, fix approved repository access/tool versions before dragging duplicate connectors into the flow.

Run development verification:

```bash
mvn -B -s .private/settings.xml -f store-api/pom.xml clean verify
```

Expected after the environment is fully configured: the three MUnit tests execute, no failure is reported and the Mule application package is produced. Inspect the test count and reports; BUILD SUCCESS with tests skipped is not the same evidence. This development build is separate from the eventual release candidate. Later the release lifecycle deliberately builds/tests/publishes a new candidate once; do not claim this earlier development package was promoted.

To try HTTP locally, run the configured application in Studio using its Run as Mule Application action (or the supported Code Builder run action). Wait for actual startup success. In another terminal:

```bash
curl -i http://localhost:8081/health
curl -i http://localhost:8081/orders/O100
curl -i http://localhost:8081/orders/UNKNOWN
```

Expected responses:

| Request | Status | JSON content |
| --- | --- | --- |
| /health | 200 | {"status":"ok","service":"beatit-store"} |
| /orders/O100 | 200 | {"orderId":"O100","sku":"0012","quantity":2,"mode":"synthetic"} |
| /orders/UNKNOWN | 404 | {"error":"not_found"} |

Whitespace/order of JSON object keys may differ; values and types must match. SKU must remain a string with both zeros. Other HTTP methods are not allowed by these listeners. Stop the local app when finished. If port 8081 is occupied, identify the conflicting process or change the local port deliberately; preserve the expected CloudHub listener configuration.

## 7. Install Jenkins and create a dedicated agent

This is a concrete local-lab controller option, not an instruction to replace your organization's managed Jenkins. Use existing approved Linux/Docker resources. A production controller needs its own access, TLS, patching, backup and network administration.

### Install Docker on a new approved Ubuntu 24.04 lab host

Skip this installation on an existing managed Docker host. On a new Ubuntu 24.04/Noble host with authorized sudo access, use Docker's official apt repository. If conflicting container packages already exist, have the administrator review Docker's prerequisite/removal guidance; do not blindly uninstall another workload's containerd or runc.

```bash
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: noble
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl status docker --no-pager
sudo docker run hello-world
```

Expected: the Docker service is active and the test container prints its confirmation then exits. If inactive, inspect service diagnostics and approved host prerequisites; an authorized administrator can start it with sudo systemctl start docker. Check the [official Ubuntu installation guide](https://docs.docker.com/engine/install/ubuntu/) for the current repository method and version pinning. This example installs the repository's available packages; production hosts need reviewed version/upgrade policy.

Use sudo for the controller Docker commands below. Do not add the Mule build account to the docker group merely to avoid sudo: Docker control is privileged. The loopback mapping below deliberately avoids public port exposure.

### Start the lab controller

With Docker installed and authorized, start a local controller:

```bash
sudo docker volume create beatit-jenkins-home
sudo docker run -d --name beatit-jenkins --restart unless-stopped -p 127.0.0.1:8080:8080 -v beatit-jenkins-home:/var/jenkins_home jenkins/jenkins:lts-jdk21
sudo docker ps --filter name=beatit-jenkins
```

Expected: the container is running and port 8080 is bound to loopback on this lab host. The image uses Java 21 for the controller. Review/pin the approved Jenkins image version/digest for repeatable installations; a mutable lts tag is a lab bootstrap convenience, not an immutable production installation. Do not mount a Docker socket into the controller for this guide.

Open http://localhost:8080 on that host (or use an approved secure tunnel). Retrieve the initial administrator secret locally:

```bash
sudo docker exec beatit-jenkins cat /var/jenkins_home/secrets/initialAdminPassword
```

Use it only in the setup screen. Install the approved suggested plugins, create the named administrator and record the local Jenkins URL. Never paste that secret into a ticket or chat. Through Manage Jenkins → Plugins, confirm Pipeline, Git, Credentials Binding and JUnit are installed. The SSH Build Agents plugin is needed if choosing the SSH launch path below. Plugin names/UI placement can vary by supported Jenkins version; review its official documentation.

Under Manage Jenkins → Nodes, set the built-in/controller executor count to zero. Builds run on a dedicated agent. Its Jenkins agent JVM must meet the controller's supported agent Java requirements (use the reviewed Java 21 setup for this controller); the Maven build tool below separately uses JDK 17.

On the approved Linux agent host, create a dedicated non-root build account and work directory, install Git, Python, a compatible agent Java and JDK 17, plus supported Maven 3.9.x. For example, an Ubuntu administrator can install the OS Java/Python/Git packages:

```bash
sudo apt-get update
sudo apt-get install git python3 openjdk-21-jre-headless openjdk-17-jdk
sudo useradd --create-home --shell /bin/bash beatit-agent
sudo install -d -o beatit-agent -g beatit-agent /home/beatit-agent/work
```

If the account already exists, verify it instead of rerunning useradd. Install the reviewed Maven binary separately if the OS Maven is outside 3.9.0–3.9.15. Record actual Java/Maven paths. Do not grant the build account unrestricted sudo.

Create a node: Manage Jenkins → Nodes → New Node → Permanent Agent. Set name beatit-mule-agent, remote root /home/beatit-agent/work, executors 1, label beatit-mule-trusted and usage “only jobs with matching labels.” Choose an approved launch method. For SSH launch, configure a dedicated SSH credential, verified host key and the agent-Java path; allow only the needed controller-to-agent route. For an inbound/WebSocket agent, use the node screen's generated supported launch instructions and protect its secret as a local secret file/service configuration. Do not commit or echo that secret. The controller container must be able to reach the configured SSH host, or the inbound agent must reach the controller URL; localhost inside a container is not the host's SSH service.

Expected: the node status is online and a harmless diagnostic job runs as the non-root agent account. If offline, inspect host routing, launch Java, verified SSH key/host key or inbound URL before adding deployment secrets.

Under Manage Jenkins → Tools:

1. Add a JDK named jdk17. Use the actual installed JDK 17 home on the agent; do not select the controller's Java 21 home by accident.
2. Add Maven named maven3. Use the reviewed supported installation or approved automatic installer version.
3. Save. On that node, verify java -version and mvn -version using the configured tools.
4. Check Python is at least 3.10 for Path.is_relative_to and that Git can read the canonical repository.

A label is scheduling metadata, not a security sandbox. Do not run untrusted pull requests, unrelated users' processes or hostile jobs on this release agent/account. Credential environment values can be readable to other same-account processes. Use separate isolated agents/folders and restricted job permissions; one executor alone is not complete isolation.

## 8. Create Jenkins credentials and a trusted main-only job

Create a protected Jenkins folder for this delivery job and restrict who can configure jobs, change SCM definitions, update credentials or approve releases. An administrator should create the credential entries from section 5 using Manage Jenkins/folder → Credentials → Add Credentials. For the connected apps choose Username with password: username is the client ID and password is the client secret. Use the exact credential IDs shown in the table. The SCM credential uses the kind your approved Git transport requires.

Do not put secrets in the Jenkinsfile or pipeline parameters. The supplied pipeline binds Exchange credentials only around publication, and deployment credentials only around the corresponding deployment stage. Shell blocks disable command tracing. These controls reduce accidental logging; they do not make hostile build code safe. Never run Maven debug/effective-settings output with live credentials.

Configure the job:

1. Choose New Item → Pipeline, name it store-main-release and place it in the protected folder.
2. Set Definition to Pipeline script from SCM.
3. Choose Git, enter the canonical repository URL and select the read-only SCM credential.
4. Set the branch specifier to the protected main branch (commonly */main in the Git plugin). Do not expose an arbitrary branch/ref build parameter for this credentialed job.
5. Set Script Path to Jenkinsfile because these starter files are the repository root. If you deliberately keep them in a subfolder, adjust both Script Path and working-directory behavior consistently.
6. Review the whole canonical source revision: Jenkinsfile, POMs, Python scripts, tests and dependencies can execute code. Require review before main changes. A stage named “trusted main” and checkout scm do not enforce trust by themselves.
7. Replace BOTH REPLACE_WITH_RELEASE_APPROVER_IDS values in Jenkinsfile with the approved Jenkins user IDs/group configuration recognized by your authorization setup. Commit/review that change. The configured preflight fails closed while these placeholders remain.
8. Save without enabling deployment parameters initially. Do not add periodic deployment triggers.
9. Run a separate uncredentialed verification job for pull requests, using isolated agents and no publisher/deployer credentials. Do not attach this release job to fork or untrusted branch discovery.

For the first protected main build, leave DEPLOY_SANDBOX and PROMOTE_PRODUCTION false. The pipeline still **publishes** the application release to Exchange, so execute it only after publication is authorized and the parent release exists. Increment the child version for a genuinely new release. Do not rerun a published immutable version expecting silent replacement.

The checkout stage cleans the workspace before checking out source. Later stages do not clean or rebuild between candidate creation and deployment. The Maven command inside the release stage is:

```bash
mvn -B -s PROTECTED_SETTINGS_PATH -f store-api/pom.xml clean deploy
```

This is one lifecycle that builds the application, runs the configured MUnit tests and publishes to the child distribution repository. No muleDeploy flag is present, so that stage is not a CloudHub deployment. Do not insert a second package/build lifecycle between publication and promotion while retaining a same-bytes claim.

Expected stage results:

| Stage | Expected evidence |
| --- | --- |
| Checkout | Exact reviewed canonical commit |
| Offline checks | Structural checks and local stub tests; supported tool versions |
| Build/test/publish | Three MUnit tests actually execute; no failures/skips; Exchange release published |
| Archive | One deployable JAR, manifest and reviewed POM references |
| Deployment stages | Skipped while parameters are false |

The JUnit post action expects real reports under store-api/target/surefire-reports. If your supported MUnit setup writes elsewhere, confirm the actual documented report path and adjust the glob deliberately. Missing reports fail this template; do not set allowEmptyResults true to disguise missing tests.

The candidate script selects exactly one deployable *-mule-application.jar, refuses snapshots/unconfigured coordinates and records SHA-256, GAV, commit and hashes of control files. The archive is a trusted pipeline artifact, not a cryptographic signature or a defense against a malicious Jenkins administrator. Protect its provenance and retention.

## 9. Deploy the existing candidate to CloudHub 2 sandbox

Before this stage, confirm all of the following: the exact parent and app releases exist in Exchange, the approved target/environment/configuration are correct, capacity/entitlement is available, the deployment identity has the required scoped permissions, and the candidate manifest matches the reviewed package. CloudHub 2 requires the application to be published in Exchange before this plugin path deploys it.

To deploy as part of the protected candidate pipeline, choose DEPLOY_SANDBOX true when starting the authorized new-candidate release run. Leave production false. At the input gate, the designated approver reviews the manifest, target and complete property set. Approval is a real release decision, not a generated message.

The pipeline binds the Exchange reader and sandbox deployer, creates a temporary settings file containing environment references and invokes:

```bash
python3 scripts/deploy_existing.py --environment environments/sandbox.json --settings PROTECTED_SETTINGS_PATH --confirm
```

This command is operational: it can create/update a CloudHub application. The Python script validates configured input, checks the candidate and control-file hashes, compares the authenticated Exchange release bytes and then invokes the Mule Maven Plugin's no-rebuild deployment goal for the existing artifact. Credential values are supplied through the child POM's environment expressions, never -D secret arguments.

For a separately approved local operator session, the same script requires protected EXCHANGE_CLIENT_ID/SECRET and DEPLOY_CLIENT_ID/SECRET environment bindings plus an actual settings path. The deployment script does not acquire permissions or create an account for you.

The remote comparison uses the reviewed US/EU Exchange HTTPS repository, verifies TLS and refuses redirects before forwarding any credentials. If the repository legitimately redirects to another location, verification stops. Do not disable the guard or forward Basic credentials to a CDN to “fix” it. Have the integration owner verify the official supported download path and implement/test a safe authenticated retrieval design before continuing. This path has not been exercised against a live tenant in this package.

Expected after an actual successful deployment: Runtime Manager shows the intended CloudHub 2 application, environment, target and runtime. Confirm the selected release coordinates and complete properties there. A plugin deployment status does not prove every business dependency works. If deployment times out, inspect Runtime Manager/operation state before retrying; an unsuccessful client response is not proof that nothing was deployed.

## 10. Verify with Postman or the included HTTP requests

Import clients/store.postman_collection.json into Postman. It contains three GET requests and explicit assertions. The collection's base_url initially points to http://localhost:8081 for local testing.

1. Create/select a training environment and set base_url to the actual approved HTTPS application URL shown for your CloudHub 2 deployment. Do not guess a hostname from a CloudHub 1 pattern.
2. Confirm that environment is selected before running the collection.
3. Run GET health and expect status 200 with status ok and service beatit-store.
4. Run GET O100 and expect the full synthetic object from section 6. The assertion checks SKU is a string equal to 0012.
5. Run GET UNKNOWN and expect 404 with the safe not_found object.
6. Retain sanitized results, application/release identity, time and target. Do not export live tokens or private environment values.

An editor with an HTTP request extension can use clients/store.http instead; change only its approved baseUrl. Curl can test the actual HTTPS base URL too:

```bash
curl -i "$APPROVED_BASE_URL/health"
curl -i "$APPROVED_BASE_URL/orders/O100"
curl -i "$APPROVED_BASE_URL/orders/UNKNOWN"
```

Expected output means status/body/type agreement, not exact JSON whitespace. A healthy /health endpoint alone does not validate O100/404 behavior. If the SKU becomes 12, fail the mapping acceptance even if every HTTP request returns quickly.

This is a small synthetic smoke suite. It does not establish production authentication, authorization, resilience, real inventory connectivity or load capacity. Do not use the production promotion stage for an unsecured starter merely because it exists in the template.

## 11. Promote the same release, or restore a reviewed candidate

Production promotion defaults off. For an authorized real application that has passed its full security/operational gate, the pipeline requires this run's sandbox path, separate human approval and production-scoped credentials. It uses the same candidate JAR, manifest and GAV; only reviewed environment inputs differ. The remote/local digest and control-file checks run again. No clean, package or deploy lifecycle rebuild is part of the promotion command.

The template's manual gate asks the reviewer to inspect actual sandbox evidence. It does not automatically prove that the person ran Postman, checked logs or assessed all required criteria. Establish your organization's accountable acceptance process rather than treating the input button as evidence by itself.

For a fresh agent or rollback, restore the complete reviewed source commit, including parent/child POMs and scripts; restore the archived manifest and JAR at their original paths. Restore the appropriate reviewed non-secret environment configuration separately. Resolve the exact published parent from Exchange with protected read access, then run:

```bash
python3 scripts/candidate.py verify
```

Do not copy only the JAR and assume a warm Maven cache will supply the correct parent. Do not regenerate a manifest around arbitrary bytes and call it the original candidate. The control-file hashes intentionally reject edited deployment code/POMs; an intentional change requires a newly reviewed candidate and manifest.

A previous release can be deployed under an approved rollback plan using its complete original candidate. Do not run the whole release-build pipeline again just to deploy old bytes: that would rebuild and attempt republication. The direct existing-candidate script is the explicit recovery path after restoration and review.

Rollback restores application/configuration state; it does not reverse external shipments, messages or incompatible database changes. Define trigger, owner, prior artifact/configuration, compatibility checks and business reconciliation before release. After restoring, repeat the authorized smoke checks and observe service behavior. Never mark uncertain effects complete solely to close a release ticket.

## 12. Troubleshoot by the failing boundary

| Symptom | Inspect first | Safe next step |
| --- | --- | --- |
| git push rejected | Canonical URL, permissions, bootstrap policy, existing history | Follow authorized repository bootstrap/review process; do not force-push |
| Parent not resolvable | Literal GAV, published parent, bootstrap repository, settings ID | Verify exact parent availability with fresh-cache check |
| Maven cannot recognize mule-application | Child extension activation and plugin repository | Keep extensions true and resolve compatible MMP |
| Unsupported Java/Maven | mvn -version and selected plugin/runtime matrix | Correct tool selection; do not skip compatibility checks |
| Exchange 401/403 | Connected-app identity, asset business group and scope | Review scoped permissions without printing credentials |
| Exchange 412 for parent | Parent publication profile/pre-deploy execution | Inspect the declared lifecycle and exact parent version |
| Existing release conflict | Parent/app version independently | Verify existing release or publish a genuinely new version |
| No MUnit reports | Test activation, runtime, skip flags and real output directory | Fix configuration; do not accept empty results |
| Jenkins agent offline | Agent JVM, network/launch URL, SSH/host key and account | Repair agent prerequisites before adding release secrets |
| Jenkins waits for label | Node online, usage restriction, exact label | Correct scheduling configuration; do not run release on controller |
| Placeholder preflight fails | Both POMs and both approver IDs | Replace reviewed values; do not delete validation |
| Candidate hash/GAV mismatch | Restored JAR, POMs, control files and trusted manifest | Stop; restore the right candidate or review a new release |
| Exchange redirect | Actual endpoint/download contract | Stop; never forward credentials to an unreviewed host |
| CloudHub target not found | Actual target name, business group/environment visibility | Confirm existing approved target; do not provision paid capacity automatically |
| Deployment succeeds but HTTP fails | Runtime logs, listener port, generated URL, ingress configuration | Diagnose exact boundary with sanitized evidence |
| Missing existing properties after redeploy | Complete managed property/secret set | Restore approved configuration; do not assume merge behavior |
| Deployment timeout | Runtime Manager's actual state | Reconcile before retrying or rolling back |

Do not use debug logs containing credentials as a shared troubleshooting artifact. Capture safe error category, command shape, tool versions, candidate identity and relevant sanitized configuration. A green Jenkins icon, an uploaded asset and a healthy business path are different checkpoints.

## 13. Complete the exercise and retain honest evidence

Deliver the reviewed Git history, configured parent/child templates, three observed MUnit results when you can execute them, protected Jenkins configuration references, release manifest and actual sandbox smoke evidence. If you only inspect offline, submit the Python test output and a clearly labeled unexecuted deployment plan instead. Both are useful; they establish different things.

Change one condition deliberately: alter the expected SKU to numeric 12 and demonstrate the intended failing MUnit/assertion behavior in your authorized local environment. Restore the contract and rerun. Separately change one archived candidate byte in a disposable copy and observe the local digest rejection without contacting any service. Do not tamper with a real release repository.

This package provides a coherent starter and bounded delivery mechanism, not a production certification. Runtime dependencies, entitlement, repository behavior, Jenkins Groovy/plugin execution, actual MUnit tests and CloudHub end-to-end behavior remain to be verified under your authorized account. No video generation, cloud purchase or actual deployment was performed while authoring it.

## Public references checked for this original walkthrough

These are current public platform references, not proprietary course materials. Confirm the applicable version and tenant controls when implementing:

- [GitHub repository quickstart](https://docs.github.com/en/repositories/creating-and-managing-repositories/quickstart-for-repositories) and [remote management](https://docs.github.com/en/get-started/git-basics/managing-remote-repositories).
- [Azure Repos creation](https://learn.microsoft.com/en-us/azure/devops/repos/git/create-new-repo?view=azure-devops), [authentication](https://learn.microsoft.com/en-us/azure/devops/repos/git/auth-overview?view=azure-devops) and [repository permissions](https://learn.microsoft.com/en-us/azure/devops/repos/git/set-git-repository-permissions?view=azure-devops).
- [Maven POM reference](https://maven.apache.org/pom.html), [lifecycle](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html) and [dependency management](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html).
- [Jenkins Docker installation](https://www.jenkins.io/doc/book/installing/docker/), [agents](https://www.jenkins.io/doc/book/using/using-agents/), [Jenkinsfile](https://www.jenkins.io/doc/book/pipeline/jenkinsfile/) and [Pipeline syntax](https://www.jenkins.io/doc/book/pipeline/syntax/).
- [MMP 4.10.3 release requirements](https://docs.mulesoft.com/release-notes/mule-maven-plugin/mule-maven-plugin-4.10.3-release-notes), [MUnit 3.7.5 compatibility](https://docs.mulesoft.com/release-notes/munit/munit-3.7.5-release-notes) and [HTTP connector releases](https://docs.mulesoft.com/release-notes/connector/connector-http).
- [MUnit Maven setup](https://docs.mulesoft.com/munit/latest/munit-maven-plugin), [Exchange Maven publication/parent assets](https://docs.mulesoft.com/exchange/to-publish-assets-maven) and [Mule Maven goals](https://docs.mulesoft.com/mule-runtime/latest/mmp-concept).
- [CloudHub 2 deployment](https://docs.mulesoft.com/mule-runtime/latest/deploy-to-cloudhub-2), [CloudHub 2 permissions](https://docs.mulesoft.com/cloudhub-2/ps-assign-permissions), [connected-app creation](https://docs.mulesoft.com/access-management/creating-connected-apps-dev) and [permission catalog](https://docs.mulesoft.com/access-management/permissions-by-product).




## Optional Azure Artifacts Maven repository extension

The original Exchange/CloudHub path above remains intact. See the [Azure Artifacts feed walkthrough](/resources/mulesoft/from-scratch-cloudhub/azure-artifacts/README.md) for private Maven parent storage and optional exact application-archive copies, plus [standalone Azure templates](/resources/mulesoft/beatit-azure-artifacts-cloudhub.zip). Azure Repos stores source; Azure Artifacts stores packages; CloudHub 2 still requires the original Exchange release and deployment approvals.
