This post walks through setting up a simple CI/CD pipeline using GitHub Actions that automatically deploys a Node.js app to SAP BTP Cloud Foundry on every push to main.
We'll deploy a minimal Node.js Express app with a simple Fiori-styled frontend. Nothing fancy — the point is the pipeline, not the app.
The project structure is:
btp-cicd-demo/
├── .github/
│ └── workflows/
│ └── deploy.yml
├── public/
│ └── index.html
├── server.js
├── package.json
├── manifest.yml
└── .gitignoreserver.js
const express = require("express");
const path = require("path");
const app = express();
app.use(express.static(path.join(__dirname, "public")));
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "public", "index.html"));
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));package.json
{
"name": "btp-cicd-demo",
"version": "1.0.0",
"description": "BTP CI/CD Demo App",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^5.2.1"
}
}manifest.yml
applications:
- name: btp-cicd-demo
memory: 128M
buildpacks:
- nodejs_buildpackpublic/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>BTP CI/CD Demo</title>
<script
id="sap-ui-bootstrap"
src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
></script>
<script>
sap.ui.getCore().attachInit(function () {
sap.ui.require(["sap/m/App", "sap/m/Page", "sap/m/Text"], function (App, Page, Text) {
new App({
pages: [
new Page({
title: "BTP CI/CD Demo",
content: [
new Text({ text: "Deployed via SAP CI/CD Service" })
]
})
]
}).placeAt("content");
});
});
</script>
</head>
<body class="sapUiBody sapUiSizeCompact">
<div id="content"></div>
</body>
</html>.gitignore
node_modules/
.envInitialise the repo, commit everything, and push to GitHub.
Create .github/workflows/deploy.yml:
name: Deploy to Cloud Foundry
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install CF CLI
run: |
curl -L "https://packages.cloudfoundry.org/stable?release=linux64-binary&version=v8&source=github" | tar -zx
sudo mv cf8 /usr/local/bin/cf
- name: Deploy to Cloud Foundry
env:
CF_API: https://api.cf.ap10.hana.ondemand.com
CF_USERNAME: ${{ secrets.CF_USERNAME }}
CF_PASSWORD: ${{ secrets.CF_PASSWORD }}
CF_ORG: <your-org>
CF_SPACE: <your-space>
run: |
cf login -a $CF_API -u $CF_USERNAME -p $CF_PASSWORD -o "$CF_ORG" -s "$CF_SPACE"
cf push btp-cicd-demoUpdate CF_API with your region endpoint, and CF_ORG and CF_SPACE with your actual org and space names. These are visible when you run cf target.
The pipeline uses two secrets to authenticate with Cloud Foundry without hardcoding credentials in the workflow file. In your GitHub repo go to Settings, then Secrets and variables, then Actions, and add two repository secrets:
CF_USERNAME — your BTP account email address CF_PASSWORD — your BTP account password
These are encrypted by GitHub and injected into the workflow at runtime. They never appear in logs or the codebase.
Note: this approach requires a non-SSO login. If your account authenticates via a corporate identity provider, you will need a technical user created by your BTP administrator for pipeline use.
Push the workflow file to main:
git add .
git commit -m "Add CI/CD workflow"
git pushGo to the Actions tab in your GitHub repo. You should see the workflow trigger immediately. Click on it to watch the steps run — checkout, CF CLI install, login, and push.
If everything is configured correctly you'll see a green tick and your app will be live at your CF route.
Once deployed, loading the app URL in your browser should give you something like this:
To stop the app without deleting it:
cf stop btp-cicd-demoTo delete the app and its route entirely:
cf delete btp-cicd-demo -f -rTo disable the GitHub Actions pipeline so it stops triggering on push, go to the Actions tab in your GitHub repo, find the workflow, click the three dots menu and select Disable workflow.
To prove the pipeline works end to end, make a change to index.html — something obvious like updating the page title — commit and push:
git add .git commit -m "Update page title"git pushGo to the Actions tab in GitHub. You should see the workflow trigger immediately against your commit:
Once the pipeline completes, reload the app URL and you should see the change reflected straight away — no manual deployment needed:
Every push to main now triggers an automatic deployment to Cloud Foundry. No manual cf push, no forgetting to deploy, no environment drift between what's in the repo and what's running.
It's a simple starting point. From here you can add a build step, run tests before deploying, deploy to multiple spaces (dev, test, prod) based on branch, or introduce the MTA build tool for more complex landscapes.
Full Repo: https://github.com/neilaspin/btp-cicd-demo
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
| User | Count |
|---|---|
| 73 | |
| 21 | |
| 21 | |
| 20 | |
| 18 | |
| 16 | |
| 16 | |
| 15 | |
| 15 | |
| 10 |