---
description: >
  A comprehensive guide on deploying applications built with SAP Cloud Application Programming Model (CAP) to SAP BTP Cloud Foundry environment.
---

# Deploy to Cloud Foundry

{{ $frontmatter.description }}

[[toc]]

## Intro & Overview

After completing the functional implementation of your CAP application by following the [Getting Started](../../get-started/bookshop) or [Cookbook](../) guides, you finally deploy it to the cloud for production. The essential steps are illustrated in the following graphic:

![First prepare for production (once) and then freeze your dependencies (once and on upgrades). Next build and assemble and then deploy.](assets/deploy-setps.drawio.svg){style="margin: 30px auto"}

First, you apply these steps manually in an ad-hoc deployment, as described in this guide. Then, after successful deployment, you automate them using [CI/CD pipelines](cicd).

## Prerequisites

The following sections are based on a new project that you can create like this:

::: code-group
```sh [Node.js]
cds init bookshop --nodejs --add sample
cd bookshop
```
```sh [Java]
cds init bookshop --java --add sample
cd bookshop
```
:::

::: details Alternatively, use the ready-to-deploy sample project
::: code-group
```sh [Node.js]
git clone https://github.com/capire/bookshop
cd bookshop
```
```sh [Java]
git clone https://github.com/sap-samples/cloud-cap-samples-java
cd cloud-cap-samples-java
```
:::

<br>

In addition, you need to prepare the following:

#### 1. SAP BTP with SAP HANA Cloud Database Up and Running {#btp-and-hana}

- Access to [SAP BTP, for example a trial](https://developers.sap.com/tutorials/hcp-create-trial-account.html)
- An [SAP HANA Cloud database running](https://help.sap.com/docs/hana-cloud/sap-hana-cloud-administration-guide/create-sap-hana-database-instance-using-sap-hana-cloud-central) in your subaccount <!--, mapped to your space -->
- Entitlement for [`hdi-shared` service plan](https://help.sap.com/docs/hana-cloud/sap-hana-cloud-getting-started-guide/set-up-schema-or-hdi-container-cloud-foundry) for your subaccount
- A [Cloud Foundry space](https://help.sap.com/docs/btp/sap-business-technology-platform/create-spaces?version=Cloud)
<!-- - A Cloud Foundry quota plan assigned to your space -->
::: tip Starting the SAP HANA database takes several minutes
Therefore, do these steps early on. In trial accounts, you need to start the database **every day**.
:::

#### 2. Latest Versions of `@sap/cds-dk` {#latest-cds}

Ensure you have the latest version of `@sap/cds-dk` installed globally:

```sh
npm -g outdated       #> check whether @sap/cds-dk is listed
npm i -g @sap/cds-dk  #> if necessary
```

For Node.js projects, ensure that the latest version of `@sap/cds` is installed in your project:

```sh
npm outdated          #> check whether @sap/cds is listed
npm i @sap/cds        #> if necessary
```

#### 3. Cloud MTA Build Tool {#mbt}

- Run `mbt` in a terminal to check whether you've installed it.
- If not, install it according to the [MTA Build Tool's documentation](https://sap.github.io/cloud-mta-build-tool/download).
- For macOS/Linux machines, it's best to install using `npm`:

  ```sh
  npm i -g mbt
  ```

- For Windows, [please also install `GNU Make`](https://sap.github.io/cloud-mta-build-tool/makefile/).

#### 4. Cloud Foundry CLI w/ MTA Plugins {#cf-cli}

- Run `cf -v` in a terminal to check whether you've installed version **8** or higher.
- If not, install or update it according to the [Cloud Foundry CLI documentation](https://github.com/cloudfoundry/cli#downloads).
- In addition, ensure to have the [MTA plugin for the Cloud Foundry CLI](https://github.com/cloudfoundry-incubator/multiapps-cli-plugin/tree/master/README.md) installed.

   ```sh
   cf add-plugin-repo CF-Community https://plugins.cloudfoundry.org
   cf install-plugin -f multiapps
   cf install-plugin -f html5-plugin
   ```

## Prepare for Production

If you followed CAP's grow-as-you-go approach, you've developed your application with an in-memory database and basic (mocked) authentication. In the cloud, you typically use production-grade services like SAP HANA and authentication providers.

The `cds add <facets>` command ensures required services are configured correctly and their dependencies are added to your _package.json_.

### 1. SAP HANA Database

While you used SQLite (Node.js) or SQLite/H2 (Java) as a low-cost stand-in during development, you use an SAP HANA Cloud database for production:

```sh
cds add hana
```

[Learn more about using SAP HANA for production.](../databases/hana){.learn-more}

### 2. Authorization/Authentication

Configure your app for XSUAA-based authentication:

```sh
cds add xsuaa
```

::: tip This will also generate an `xs-security.json` file
The roles/scopes are derived from authorization-related annotations in your CDS models. Ensure to rerun `cds compile --to xsuaa`, as documented in the [_Security_ guide](../security/cap-users#xsuaa-roles) whenever there are changes to these annotations.
:::

[Learn more about SAP Authorization and Trust Management/XSUAA.](https://discovery-center.cloud.sap/serviceCatalog/authorization-and-trust-management-service?region=all){.learn-more}

### 3. Remote Service Consumption {#remote-services}

CAP supports two HTTP clients for remote service calls.

#### SAP Cloud SDK {#add-cloud-sdk}

If you intend to consume remote services in production, for example, via [BTP Destinations](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/destination-service), add the requisite SAP Cloud SDK packages, like that for Node.js:

```shell
npm add @sap-cloud-sdk/connectivity
npm add @sap-cloud-sdk/http-client
npm add @sap-cloud-sdk/resilience
```

[Learn more about consuming remote services with SAP Cloud SDK.](https://sap.github.io/cloud-sdk/docs/js/overview){.learn-more}

#### Native Fetch Client <Beta /> {#native-fetch}

CAP provides a built-in remote client that uses the native Node.js `fetch` API. For limitations, see the warning below. During local development, you don't need SAP Cloud SDK, but you can still use it. For production, you still need SAP Cloud SDK. For example, you use it to resolve named destinations through the SAP BTP Destination service.

CAP selects the native fetch client for each outgoing request according to the following rules:

1. If the destination requires features only available in SAP Cloud SDK (for example, SAP BTP Destination service resolution or non-basic authentication), CAP always uses SAP Cloud SDK.
2. If you explicitly set <Config>cds.remote.native_fetch</Config> to `true` or `false`, CAP uses that setting.
3. Otherwise, CAP uses native fetch when you haven't installed `@sap-cloud-sdk/http-client`.

::: warning Current limitations
The native fetch client does not yet support named destinations using the SAP BTP Destination service. It supports only [application-defined destinations](../services/consuming-services#use-application-defined-destinations). In addition, it limits authentication to `NoAuthentication` and `BasicAuthentication`.
:::


### 4. MTA-Based Deployment {#add-mta-yaml}

You use the [Cloud MTA Build Tool](https://sap.github.io/cloud-mta-build-tool/) to execute the deployment. The modules and services are configured in an _mta.yaml_ deployment descriptor:

```sh
cds add mta
```

[Learn more about MTA-based deployment.](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/d04fc0e2ad894545aebfd7126384307c.html?locale=en-US){.learn-more}

### 5. User Interfaces {#add-ui}

#### Option A: SAP Cloud Portal

If you intend to deploy **multitenant** applications with a UI, set up the [HTML5 Application Repository](https://discovery-center.cloud.sap/serviceCatalog/html5-application-repository-service) in combination with the [SAP Cloud Portal service](https://discovery-center.cloud.sap/serviceCatalog/cloud-portal-service):

```sh
cds add portal
```

::: tip `cds add portal` adds an _App Router_ configuration to your project
The App Router acts as a single point-of-entry gateway to route requests to.
In particular, it ensures user login and authentication in combination with XSUAA or IAS.
:::

#### Option B: SAP BTP Application Frontend <Beta />

For **single-tenant** applications, you can use the new [SAP BTP Application Frontend](https://help.sap.com/docs/application-frontend-service) service:

```sh
cds add app-frontend
```
[Enable the service for consumption in your subaccount](https://help.sap.com/docs/application-frontend-service/application-frontend-service/enabling-service?locale=en-US){.learn-more}

::: details Other deployment variants...

For **single-tenant** applications, you can integrate with SAP Build Work Zone, standard edition:

```sh
cds add workzone
```

This approach uses the **managed App Router** provided by SAP Fiori Launchpad — you don't need to deploy your own. Instead, destinations are configured.

<br>

You might also use a custom App Router setup without SAP BTP Cloud Portal service:

```sh
cds add approuter
```
[Learn more about the SAP BTP Application Router.](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/01c5f9ba7d6847aaaf069d153b981b51.html?locale=en-US){.learn-more}
<br>
However, in this case, you need to create symlinks from your _app_ folders to make them visible to the deployed App Router.
The [samples _modulith_](https://github.com/capire/samples) project uses this setup for serving a static _index.html_ consuming Vue.js via CDN.

[Find the symlink directory in the App Router's _resources_ folder](https://github.com/capire/samples/tree/main/.deploy/app-router/resources){.learn-more}
:::

### 6. Optional: Multitenancy { #add-multitenancy }

To enable multitenancy for production, run the following command:

```sh
cds add multitenancy
```

<br>

::: tip You're set!
The previous steps are required _only once_ in a project's lifetime. With that done, we can repeatedly deploy the application.
:::

<br>


## Build and Deploy

Make sure you are logged in to Cloud Foundry and target the space you want to deploy to:
```sh
cf login --sso  # to log on with SAP Universal ID
cf target
```
[Learn more about `cf login`](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/7a37d66c2e7d401db4980db0cd74aa6b.html){.learn-more}


If your project already includes a _package-lock.json_, freeze your updated dependencies:

```sh
npm install --package-lock-only
```

You can now build and deploy the application:

```sh
cds up
```

::: details Essentially, this automates the following steps...

```sh
# Installing app dependencies, e.g.
npm i app/browse
npm i app/admin-books

# If project is monorepo
ln -sf ../package-lock.json

# If project is multitenant
npm i --package-lock-only --prefix mtx/sidecar

# If package-lock.json doesn't exist
npm i --package-lock-only

# Final assembly and deployment...
mbt build -t gen --mtar mta.tar
cf deploy gen/mta.tar -f
```
:::

::: details Test with `cds build`

While `cds build` is already run as part of `mbt build` in `cds up`, you can also run it standalone to inspect what is generated for production:

```sh
cds build --production
```

[Learn more about running and customizing `cds build`.](build){.learn-more}

:::

[Got errors? See the troubleshooting guide.](../../get-started/get-help#mta){.learn-more}
[Learn how to reduce the MTA archive size **during development**.](../../get-started/get-help#reduce-mta-size){.learn-more}

This process can take some minutes and finally logs an output like this:

```log
[…]
Application "bookshop" started and available at
"[org]-[space]-bookshop.<landscape-domain>.com"
[…]
```

You can use this URL to access the App Router as the entry point of your application.

For **multitenant applications**, you have to subscribe a tenant first. The application is accessible via a tenant-specific App Router URL after subscription.

::: info SaaS Extensibility
Share the generic App-Router URL with SaaS consumers for logging in as extension developers using `cds login` or other [extensibility-related commands](../extensibility/customization#prep-as-operator).
:::

::: tip No index page and SAP Fiori preview in the cloud
The default index page and [SAP Fiori preview](../uis/fiori#fiori-preview), that you're used to seeing during local development, are meant only for the development profile and aren't available in the cloud. For productive applications, you should add a proper SAP Fiori elements application through one of the [user interface options](#add-ui) outlined before.
:::

### Inspect Apps in BTP Cockpit

Visit the "Applications" section in your [SAP BTP cockpit](https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/144e1733d0d64d58a7176e817fa6aeb3.html) to see the deployed apps:

![The screenshot shows the SAP BTP cockpit, when a user navigates to their dev space in the trial account and views all deployed applications.](./assets/apps-cockpit.png)

::: tip Next up: Assign the _admin_ role
To access the admin APIs, assign the _admin_ role required by the `AdminService`. By default, CAP creates a **role collection** named _admin‑\<org\>‑\<space\>_. [Assign it to your user](https://help.sap.com/docs/btp/sap-business-technology-platform/assign-user-groups-to-role-collections) to get access.
:::

### Use MTA Extensions with `cds up`

For Cloud Foundry deployments, you can pass an [MTA extension descriptor](https://help.sap.com/docs/btp/sap-business-technology-platform/defining-mta-extension-descriptors) to `cds up` using `--overlay`:

```sh
cds up --overlay .deploy/eu10-prod.mtaext
```

This allows you to keep landscape-specific deployment settings outside your base _mta.yaml_, for example, scaling parameters:

```yaml [eu10-prod.mtaext]
_schema-version: 3.3.0
ID: bookshop-eu10-prod
extends: bookshop

modules:
  - name: bookshop-srv
    parameters:
      instances: 2
```


## Staying Up-to-date { #freeze-dependencies }

Deployed applications should freeze all their dependencies, including transient ones. Therefore, on first execution, `cds up` creates a _package-lock.json_ file for all application modules.

It is **essential to regularly update dependencies** to consume latest bug fixes and improvements. Not doing so will increase the risk of **security vulnerabilities**, expose your application to **known bugs**, and make future upgrades significantly harder and more time-consuming.

We recommend setting up [Dependabot](https://docs.github.com/en/code-security/dependabot), [Renovate](https://docs.renovatebot.com/) or similar automated solutions to update dependencies **one-by-one** to easily identify breaking changes, minimize risks, and ensure continuous compatibility and **stability of your application**.



## Upgrade Tenants in Java

The CAP Java SDK offers `main` methods for Subscribe/Unsubscribe in the classes `com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe` that can be called from the command line. This way, you can run the tenant subscribe/unsubscribe for the specified tenant. This triggers your custom handlers, which is useful for local testing scenarios.

To register all handlers of the application properly during the execution of a tenant operation `main` method, the component scan package must be configured. To set the component scan, the property <Config java>cds.multitenancy.component-scan</Config> must be set to the package name of your application.

The handler registration provides additional information that is used for the tenant subscribe, for example, messaging subscriptions that are created.

::: warning The MTX sidecar must be running
You can stop the CAP Java backend when you call this method, but the MTX sidecar application must be running!
:::

You can also automate this synchronization, for example using [Cloud Foundry Tasks](https://docs.cloudfoundry.org/devguide/using-tasks.html) on SAP BTP and [Module Hooks](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/b9245ba90aa14681a416065df8e8c593.html) in your MTA.

The `main` method optionally takes tenant ID (string) as the first input argument and tenant options (JSON string) as the second input argument. Alternatively, you can use the environment variables `MTCOMMAND_TENANTS` and `MTCOMMAND_OPTIONS` instead of arguments. The command-line arguments have higher priority, so you can use them to override the environment variables.

The method returns the following exit codes.

| Exit Code  | Result                                                                                                              |
|-----------:|---------------------------------------------------------------------------------------------------------------------|
| 0          | Tenant subscribed/unsubscribed successfully.                                                                        |
| 3          | Failed to subscribe/unsubscribe the tenant. Rerun the procedure to make sure the tenant is subscribed/unsubscribed. |

To run this method locally, use the following command where `<jar-file>` is the one of your applications:

::: code-group

```sh [&gt;= Spring Boot 3.2.0]
java -cp <jar-file> -Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe org.springframework.boot.loader.launch.PropertiesLauncher <tenant> [<tenant options>]
```

```sh [&lt; Spring Boot 3.2.0]
java -cp <jar-file> -Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe org.springframework.boot.loader.PropertiesLauncher <tenant> [<tenant options>]
```

:::

In the SAP BTP, Cloud Foundry environment, it can be tricky to construct such a command. The reason is that the JAR file is extracted by the Java buildpack and the place of the Java executable isn't easy to determine. Also the place differs for different Java versions. Therefore, we recommend adapting the start command that is generated by the buildpack and run the adapted command:

::: code-group

```sh [&gt;= Spring Boot 3.2.0]
sed -i 's/org.springframework.boot.loader.launch.JarLauncher/org.springframework.boot.loader.launch.PropertiesLauncher/g' /home/vcap/staging_info.yml && sed -i 's/-Dsun.net.inetaddr.negative.ttl=0/-Dsun.net.inetaddr.negative.ttl=0 -Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe/g' /home/vcap/staging_info.yml && jq -r .start_command /home/vcap/staging_info.yml | sed 's/^/ MTCOMMAND_TENANTS=my-tenant [MTCOMMAND_TENANTS=<tenant options>]/' | bash
```

```sh [&lt; Spring Boot 3.2.0]
sed -i 's/org.springframework.boot.loader.JarLauncher/org.springframework.boot.loader.PropertiesLauncher/g' /home/vcap/staging_info.yml && sed -i 's/-Dsun.net.inetaddr.negative.ttl=0/-Dsun.net.inetaddr.negative.ttl=0 -Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe/g' /home/vcap/staging_info.yml && jq -r .start_command /home/vcap/staging_info.yml | sed 's/^/ MTCOMMAND_TENANTS=my-tenant [MTCOMMAND_TENANTS=<tenant options>]/' | bash
```

```sh [Java 8]
sed -i 's/org.springframework.boot.loader.JarLauncher/-Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe org.springframework.boot.loader.PropertiesLauncher/g' /home/vcap/staging_info.yml && jq -r .start_command /home/vcap/staging_info.yml | sed 's/^/ MTCOMMAND_TENANTS=my-tenant [MTCOMMAND_TENANTS=<tenant options>]/' | bash
```

:::
