Cloud Transport Management on BTP: move an app from DEV to TEST and PRD
Set up cTMS as the cloud's STMS: destinations, nodes, routes and a step-by-step release of an MTA app, with the typical errors and how to fix them.
If you come from ABAP, you know the ritual: release the transport request, it lands in the QA queue, someone imports it, it gets tested, then it moves to production. On SAP BTP that role belongs to Cloud Transport Management (cTMS): you model environments as nodes, connect them with routes, and every version of your app travels DEV → TEST → PRD through controlled imports.
| STMS (ABAP) | cTMS |
|---|---|
| Create the transport request | Upload a file (creates the transport request) |
| Objects in the request | The app’s .mtar file |
| Release the request | Upload it to the DEV node |
| QAS import queue | TEST node queue |
| Import into QAS / PRD | Manual Import in TEST / PRD |
Key difference: each
.mtaris a full deployment of the app, not a delta. The new version replaces the previous one.
In this tutorial we transport a simple Fiori app packaged as an MTA (Multi-Target Application).
The architecture
Global account
├── "Central Service" subaccount → cTMS (nodes, routes, destinations)
└── Runtime subaccount → Cloud Foundry
├── space DEV
├── space TEST
└── space PRD
- cTMS lives in its own subaccount, separate from the environments. That is the pattern SAP recommends: you control who can transport without granting access to the environments.
- On a trial account the three environments are spaces in one subaccount, because new subaccounts usually have no memory available. In a real landscape each environment would normally be a separate subaccount; the rest of the tutorial stays the same.
1. Set up cTMS
In the central subaccount:
- Entitlements: add both Cloud Transport Management plans: lite (the user interface) and standard (API access). On trial they come assigned to the
trialsubaccount: remove them there so you can assign them to the central one. - Subscription: in the Service Marketplace, subscribe to Cloud Transport Management with the lite plan.
- Roles: create a role collection (for example,
TMS Admin), add the subscription’s Administrator role template to it and assign yourself. For people who only import there is the Import Operator template. - Check: open the application from Instances and Subscriptions. You should see an empty Overview page.
2. Prepare the target environments
Before the first transport, every target must have all the services your mta.yaml requires. For this tutorial’s app:
| Service | Plan | Purpose |
|---|---|---|
| Cloud Foundry Runtime | MEMORY | Run the approuter (256 MB per environment) |
| Authorization and Trust Management (xsuaa) | application | App login |
| HTML5 Application Repository | app-host | Store the app content |
| HTML5 Application Repository | app-runtime | Serve it from the router |
| Destination Service | lite | The app’s destinations |
Check the org’s real memory with
cf org-quota <quota>: the cockpit may show the entitlement as assigned while the org still has 0 MB.
3. Destinations: where each node deploys
In the central subaccount, Connectivity → Destinations, create one per environment. They all point to the Cloud Foundry deploy service:
https://deploy-service.cf.<region>.hana.ondemand.com/slprot/<org>/<space>/slp
| Field | Value |
|---|---|
| Name | TMS_DEV, TMS_TEST, TMS_PRD |
| Type | HTTP |
| Proxy Type | Internet |
| Authentication | BasicAuthentication |
| User / Password | A technical user with the Space Developer role in the target space |
Three warnings that save hours:
- Org and space are case-sensitive.
devinstead ofDEVcauses a Not Found error on import. - “Check Connection” does not validate credentials, nor whether the org or space exist: only that the URL answers. The real test is the first import, or a
cf loginwith the same user. - Make sure each destination points to its own environment. An import can end as Succeeded and still have deployed to the wrong place: cTMS deploys wherever the destination says.
4. Nodes and routes
In the cTMS application, Transport Nodes:
| Node | Allow Upload | Forward Mode | Content Type | Destination |
|---|---|---|---|---|
| DEV | ✅ | Pre-Import | Multi-Target Application | TMS_DEV |
| TEST | ❌ | On Success | Multi-Target Application | TMS_TEST |
| PRD | ❌ | On Success | Multi-Target Application | TMS_PRD |
Leave automatic import turned off on all three: every import will be manual and controlled.
And in Transport Routes: DEV_TO_TEST (DEV → TEST) and TEST_TO_PRD (TEST → PRD).
Forward modes
| Mode | When the version moves to the next node |
|---|---|
| Pre-Import | When the import starts in the current node (not on upload) |
| Post-Import | After the import, whether it succeeded or not |
| On Success | Only if the import succeeded |
| Manual | When someone forwards it from the queue |
With this setup, a version reaches TEST as soon as you import it into DEV, and reaches PRD only if the TEST import succeeded.
5. The application: three MTA details
Any MTA works. If you use a Fiori app with its own approuter, watch these three points.
The version, on every release. cTMS identifies each transport by its file; always bump it:
_schema-version: "3.2"
ID: helloworld-mta
version: 0.0.2
A different xsappname per space. If the same app lives in several spaces of one subaccount, its XSUAA instances would clash. The ${space} variable prevents it:
- name: helloworld-mta-uaa
type: org.cloudfoundry.managed-service
parameters:
config:
tenant-mode: dedicated
xsappname: helloworld-mta-${space}
path: ./xs-security.json
service: xsuaa
service-plan: application
The redirect-uris in xs-security.json. Without them, XSUAA rejects the router’s login with OpenID provider cannot process the request:
{
"xsappname": "helloworld-mta",
"tenant-mode": "dedicated",
"scopes": [],
"role-templates": [],
"oauth2-configuration": {
"redirect-uris": ["https://*.cfapps.<region>.hana.ondemand.com/**"]
}
}
oauth2-configuration goes inside the root object, as just another property.
6. The release, step by step
-
Build and test the app (in SAP Business Application Studio, with the preview).
-
Bump
versioninmta.yaml. -
Build the archive from the MTA folder:
mbt build -
Download the
.mtarfrommta_archives/. -
In cTMS: Transport Nodes → DEV → Upload. Pick the
.mtarand write a useful description (version and requirement). -
In the DEV queue, Import Selected: it deploys to DEV and, thanks to Pre-Import, moves to the TEST queue.
-
Validate the app in DEV.
-
In the TEST queue, Import Selected. If it succeeds, it moves to the PRD queue (On Success).
-
Validate in TEST.
-
Once approved, Import Selected in the PRD queue.
Two queue actions worth telling apart: Reset lets you re-import a version already processed in that node; Remove takes it out of the queue to discard it.
7. Typical errors
| Symptom | Cause | Fix |
|---|---|---|
| Cannot create the node without a destination | A node with a Content Type (non-virtual) requires one | Create the destination before the node |
| Uploaded to DEV, nothing shows in TEST | Pre-Import forwards on import, not on upload | Import into DEV |
Error during client creation: Unauthorized |
Wrong password in the destination | Fix it; use a technical user |
Error during client creation: Not Found |
Org or space misspelled in the URL | Copy the exact names, case included |
Service plan application not found |
Missing XSUAA entitlement in the target | Assign xsuaa – application to that subaccount |
| Router Stopped, 0 MB available | The org quota did not sync with the entitlements | Check cf org-quota; on trial, use the trial subaccount’s org, which does have memory |
| Login: OpenID provider cannot process the request | redirect-uris missing in xs-security.json |
Add them, bump the version and transport again |
| Import Succeeded but the app is in another environment | The destination points to the wrong environment | Fix the URL, Reset the transport and re-import. Clean up whatever was deployed by mistake |
On a trial account, Cloud Foundry stops apps once a day. If your app does not answer,
cf start <app>.
8. Change rules
The tool is half of it; the other half is the process:
- Nobody imports into production outside the defined route.
- Whoever develops a change does not approve or import it.
- Every change that reaches PRD has a requirement, approved tests and an associated transport.
- Destinations use technical users, never personal ones.
Every change in production should be able to answer three questions: who asked for it, who tested it and who approved it.
Next step
The same cTMS transports other kinds of content. Integration Suite iFlows, for example, travel per package: transports are enabled in the Integration Suite settings, the Content Agent packages the content, and the nodes’ destinations point to the target Cloud Integration tenant. For an iFlow to adapt to each environment without editing it, start by externalizing its parameters.
Get new tutorials by email
One email when something new is published. No spam; unsubscribe anytime.
I will email you to confirm your subscription. Data handled under the privacy policy.