Technology Blog Posts by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
neilaspin
Participant
0 Likes
92

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.

What You'll Need

  • An SAP BTP account with Cloud Foundry enabled and a space to deploy to
  • A GitHub account
  • Node.js and the CF CLI installed locally
  • A non-SSO username and password for your BTP account (used by the pipeline

The App

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
└── .gitignore

server.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_buildpack

public/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/
.env

Initialise the repo, commit everything, and push to GitHub.

The Pipeline

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-demo

Update 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.

GitHub Secrets

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.

Running the Pipeline

Push the workflow file to main:

git add .
git commit -m "Add CI/CD workflow"
git push

Go 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:

Screenshot 2026-06-07 at 10.01.18.png

Cleaning Up

To stop the app without deleting it:

cf stop btp-cicd-demo

To delete the app and its route entirely:

cf delete btp-cicd-demo -f -r

To 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.

Seeing It In Action

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 push

Go to the Actions tab in GitHub. You should see the workflow trigger immediately against your commit:

Screenshot 2026-06-07 at 11.15.04.png

Once the pipeline completes, reload the app URL and you should see the change reflected straight away — no manual deployment needed:

Screenshot 2026-06-07 at 11.15.32.png

What This Gives You

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