Skip to Content
Technical Articles
Author's profile photo Uwe Klinger

The new way to consume Service Bindings on Kyma Runtime

With the Kyma Runtime, you can use services offered by the SAP Business Technology Platform (SAP BTP). Previously service bindings were limited to the SAP BTP Cloud Foundry environment. Learn how to consume them in your application running in the SAP BTP Kyma environment.


SAP BTP services run outside of your Kyma cluster, just like services you can consume from a Hyperscaler. Today it’s not possible to integrate them seamlessly in your service mesh. Even if it would be possible, other service-specific information is required in the application. Therefore, a mechanism is required to provide your application with the required information — referred to as “service credentials”.

Credentials are stored in a Kubernetes secret and mapped to a bunch of environment variables. However, this is not suitable if frameworks are used to access credentials, such as the SAP Cloud Application Programming Model (CAP) or Spring Boot.

The Cloud Foundry Approach

Cloud Foundry has a thing called “service bindings” that bind service instances to applications. The data of all services bound to an application is provided as a JSON object via an environment variable (VCAP_SERVICES). Applications just need to parse the value of that environment variable.

Service bindings consist of metadata and credentials. Metadata are, for example, the type of the service offering (label), the service plan, a list of tags, and the name of the service instance. Service Bindings

Kubernetes doesn’t have such a mechanism. And here comes into the play: It’s a community lead effort to standardize how services are bound to Kubernetes applications, or more precisely: Kubernetes workloads.

Basically, that means:

A container must have an environment variable SERVICE_BINDING_ROOT that points to the root directory for all service bindings.

For example, you set in your container spec:

    value: /bindings

Bindings have to be put in a sub directory of that directory. The sub directory has a file for each property of the binding. Usually, it’s mounted to a volume created from a Kubernetes secret.

There are some conventions for binding properties:

  1. There must be a property type that contains the type of the service. You can compare this to the label property in Cloud Foundry.
  2. Optionally, there is a property provider to indicate the provider of this service.
  3. There are rules for a few well-known properties.
  4. Any additional properties can have arbitrary content.

With that you can use type and provider to look up the desired service. For example, if you’re looking for the hana service, you can search for a binding with type hana.

Let’s have a look at an example directory structure for an SAP HANA binding:

    type: hana
    user: me
    password: secret

This works for SAP BTP services as well. Compared to Cloud Foundry, the label property containing the service type must be renamed to type.

But there are a few shortcomings that I wanted to mention:

The Metadata Extension

SAP BTP services have additional metadata, such as the service plan or tags, that you might want to use as a search criterion. For example, if your application supports different databases you could use the tag db to search for a database service.

Bindings of SAP BTP services do not just contain strings, they can be structured using sub objects and arrays, and they can have boolean or numeric properties. Kubernetes secrets do not support typing and thus you need service-specific knowledge to deserialize the credentials according to its type.

Well, that’s not a problem if you write the code for specific service bindings. But it’s a problem for frameworks and libraries that read the bindings and expose them via an API. If your application was implemented for Cloud Foundry and you want to port it to Kyma, you don’t want to change the code to read the bindings. You expect the libraries to do the job for you.

To support that, SAP has implemented an extension to It just adds another property .metadata that contains a JSON string explaining the kind and the formatting of each property. A property either belongs to the metadata, like type or plan, or to the credentials, like user or password. It can be formatted as a string or as a JSON value. String type properties are always stored with the string format. The JSON value formatting allows to store booleans, numbers, sub objects, and arrays in a binding value.

For example:

    .metadata: /* see below */
    type: funny-service
    tags: ["funny", "somewhat", "useful"]
    user: me
    a_number: 3
    a_boolean: true
    an_object: { "property1": "value 1", "property2": true }

.metadata File Content:

    "metaDataProperties": [
        { "name": "type", "format": "text" },
        { "name": "tags", "format": "json" }
    "credentialProperties": [
        { "name": "user", "format": "text" },
        { "name": "a_number", "format": "json" },
        { "name": "a_boolean", "format": "json" },
        { "name": "an_object", "format": "json" }

This helps libraries like @sap/xsenv and CAP to parse the bindings and provide them through their APIs in a compatible manner.

The good news is that it’s already implemented, and you can use it right now.

In the Kyma Runtime, secrets of SAP BTP service bindings are already created with the .metadata property and contain all metadata you know from Cloud Foundry. This was introduced with the SAP BTP Service Operator version v0.2.3.

Library Support

You can consume bindings in your Node.js, Java, and CAP Node.js applications:

Language Library Min Version
Node.js CAP 6.0.1
Node.js @sap/xsenv 3.3.2
Node.js @sap/hdi-deploy 4.4.1
Node.js @sap/html5-app-deployer 4.1.2
Node.js @sap/sbf 6.5.2
Java BTP Env Variable Access 0.4.1

These libraries allow you to consume bindings without the .metadata property extension as well. In that case, they treat all properties as strings. The properties type and provider are handled as metadata and all other properties as strings.

End-to-End Example

If you want to see how this works end-to-end, you can follow the next steps to create a service and see how it can be consumed with @sap/xsenv in your Kyma Runtime cluster.

Create a Service Instance

The first step is to create a service instance.

  1. Create a file xsuaa-instance.yaml:
     kind: ServiceInstance
       name: xsuaa-instance
       serviceOfferingName: xsuaa
       servicePlanName: application
  2. Apply it to your cluster:
     kubectl apply -f xsuaa-instance.yaml
  3. Check the state of your service instance:
     kubectl get serviceinstance
  4. After short time, it should look like this:
     NAME             OFFERING   PLAN          STATUS    READY   AGE
     xsuaa-instance   xsuaa      application   Created   True    28s

Create a Service Binding

Next up, create a service binding for your service instance. This automatically creates a Kubernetes secret with the binding metadata and credentials.

  1. For the binding, create a file xsuaa-binding.yaml:
     kind: ServiceBinding
       name: xsuaa-binding
       serviceInstanceName: xsuaa-instance
       secretName: xsuaa-secret
  2. Apply it to your cluster:
     kubectl apply -f xsuaa-binding.yaml
  3. After short time, it should look like this:
     NAME            INSTANCE         STATUS    READY   AGE
     xsuaa-binding   xsuaa-instance   Created   True    15s

Inspecting the Binding’s Secret

Now, you can inspect the Kubernetes secreted that was created by the service binding.

  1. List the created secret:
     kubectl get secret xsuaa-secret
     NAME           TYPE     DATA   AGE
     xsuaa-secret   Opaque   22     62s
  2. You can view the secret’s base64 encoded values (I hid the sensitive data in my example):
     kubectl get secret xsuaa-secret -o yaml
     apiVersion: v1
     kind: Secret
     type: Opaque
       name: xsuaa-secret
       .metadata: eyJjc...XX0=
       apiurl: <...hidden...>
       clientid: <...hidden...>
       clientsecret: <...hidden...>
       credential-type: YmluZGluZy1zZWNyZXQ=
       identityzone: <...hidden...>
       identityzoneid: <...hidden...>
       instance_guid: <...hidden...>
       instance_name: eHN1YWEtaW5zdGFuY2U=
       label: eHN1YWE=
       plan: YXBwbGljYXRpb24=
       sburl: <...hidden...>
       subaccountid: <...hidden...>
       tags: WyJ4c3VhYSJd
       tenantid: <...hidden...>
       tenantmode: c2hhcmVk
       type: eHN1YWE=
       uaadomain: <...hidden...>
       url: <...hidden...>
       verificationkey: <...hidden...>
       xsappname: <...hidden...>
       zoneid: <...hidden...>
  3. You can check what’s in the .metadata property with this small piece of shell magic:
     kubectl get secret xsuaa-secret -o json | jq -r '.data[".metadata"]' | base64 -d | jq .

Try a Service Binding API

Finally, you can create a pod using this binding to explore it, with the required configuration to consume the service binding.

  1. Create a file test-pod.yaml:
     apiVersion: v1
     kind: Pod
       name: test-pod
       - name: test
         - name: SERVICE_BINDING_ROOT
           value: /bindings
         image: node:alpine
         - mountPath: /bindings/auth
           name: xsuaa-volume
           readOnly: true
         command: [ 'sleep', "10000" ]
       - name: xsuaa-volume
           defaultMode: 420
           secretName: xsuaa-secret

    You can find the three things here that are important to access the service binding:

    1. The environment variable SERVICE_BINDING_ROOT is provided.
    2. There is a volume with the service binding secret properties.
    3. The volume is mounted in a sub directory of the directory provided in SERVICE_BINDING_ROOT.
  2. Apply the pod to your cluster:
     kubectl apply -f test-pod.yaml
  3. Wait for the pod to turn into status Running:
     kubectl get pods test-pod
     test-pod   2/2     Running   0          10s

After having the preparation completed, you can use the pod to explore the files created for the binding:

  1. Enter a shell on the pod:
     kubectl exec test-pod -it -- sh

    Now, you can execute commands on the pod.

  2. List all the service binding files, for example:
     find /bindings

    You will observe an unexpected, yet a bit weird, output: All properties exist two times!

    The files in the expected location (for example: /bindings/auth/clientid) are symbolic links to files in a sub directory starting with ...

You can also try out what the @sap/xsenv library returns:

  1. Install @sap/xsenv on the pod:
     npm i -g @sap/xsenv
  2. Read all services:
     node -e 'console.log(require("/usr/local/lib/node_modules/@sap/xsenv").readServices())'
  3. The output looks like this:
       auth: {
         instance_name: 'xsuaa-instance',
         instance_guid: '<...hidden...>',
         plan: 'application',
         label: 'xsuaa',
         type: 'xsuaa',
         tags: [ 'xsuaa' ],
         credentials: {
           sburl: '<...hidden...>',
           url: '<...hidden...>',
           verificationkey: '<...hidden...>',
           clientid: '<...hidden...>',
           clientsecret: '<...hidden...>',
           'credential-type': 'binding-secret',
           uaadomain: '<...hidden...>',
           zoneid: '<...hidden...>',
           subaccountid: '<...hidden...>',
           tenantmode: 'shared',
           xsappname: '<...hidden...>',
           apiurl: '<...hidden...>',
           identityzone: '<...hidden...>',
           identityzoneid: '<...hidden...>',
           tenantid: '<...hidden...>'
         name: 'auth'

Metadata and credential data are separated, and the tags array is correctly deserialized as expected. The API returns the same binding data like on Cloud Foundry.

Credentials can be also looked up by its metadata properties.

  1. Query the service using its label:
     node -e 'console.log(require("/usr/local/lib/node_modules/@sap/xsenv").getServices({auth: { label: "xsuaa"}}))'
  2. You should get the same output as the previous call.

If you like, you can create a second service instance and bind it and continue doing experiments.

Once you’ve finished, enter exit to leave the pod.

You can delete the created Kubernetes resources using:

kubectl delete -f xsuaa-instance.yaml,xsuaa-binding.yaml,test-pod.yaml

Remark: The value of the name Property

Note, that the name property is filled with the directory name (auth in the example above) of your binding instead of the instance name (xsuaa-instance in the example). On Cloud Foundry, you usually get the instance name.

The VCAP_SERVICE doc says:

name → The binding_name, if it exists. Otherwise, the instance_name

Accordingly, we decided to always fill it with the binding’s directory name that you can set depending on your needs. For compatibility with Cloud Foundry, you either use the instance’s name as binding directory name or change your code to use the instance_name property instead of the name property.


Wrapping it up, the adoption of increases the interoperability between SAP and non-SAP technology.

Further, it enables usage of things that may be created around that spec. For example, you could use the service binding controller to inject bindings into your pods.

With the .metadata extension, all the metadata of SAP BTP service bindings can be accessed and credentials are accessible with their correct data type. This lets you stay platform independent and migrate applications from Cloud Foundry to the Kyma Runtime without touching the binding related code.

Additionally, any applications that are capable to read bindings can also read bindings to SAP BTP services (ignoring the .metadata file).

I’d like to recommend the new tutorials showing how to Deploy Your CAP Application on SAP BTP Kyma Runtime as part of the SAP BTP end-to-end tutorial series. In the tutorials you use CAP’s tooling to add a Helm chart to an application and deploy it to a Kyma Runtime cluster. The Helm chart makes use of the new service binding mechanism.

Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo Martin Donadio
      Martin Donadio

      Hi Uwe,

      Very nice detailed blog post for this topic !

      In the case of HANA HDI-Containers ( hana service, hdi-shared plan), either the Service Manager or Service Broker should be aware of the HANA Cloud database we want to request the HDI Container.

      I believe that's why its failing in the step Create a Service Instance for HDI-Containers.

      Is there any way we can tell the SAP BTP Service Operator to use an specific HANA Cloud database for the HDI service plan ?



      Author's profile photo Uwe Klinger
      Uwe Klinger
      Blog Post Author

      Hi Martin,


      The limitation is with the HANA service itself and not with the Service Manager or SAP BTP Operator. The Service Manager receives a service definition from the service's broker ("catalog.json"). In that definition the supported platforms of the service are enumerated.

      Unfortunately, the HANA service is not yet enabled for consumption from Kyma/Kubernetes. Therefore it is not possible to create service instances, although you're entitled for it.

      There is a workaround for these kind of services: Create a CF space in the same region and create the service instance and service key there. Create a service key from the service key.

      You also need to add the meta data for the consumption as service binding in the way I explained in my blog post.

      For HANA hdi-shared, you can try this bash script that is used in the BTP end-to-end tutorial.

      You need to be logged on both on a CF space and in a Kyma or Kubernetes cluster as a prerequisite.

      Another aspect is if you can access the service from your network. HANA has IP whitelisting. You need to find out the egress IP address of your cluster and add it to the whitelist.

      You might try this command when logged on your cluster:

      kubectl run -it --rm --restart=Never --image alpine/curl nat-ip-probe --overrides='{ "apiVersion": "v1", "metadata": {"annotations": { "":"false" } } }' -- curl

      I've been told that it works on Google and AWS based landscapes.

      Best regards,

      Author's profile photo Rafael Caziraghi
      Rafael Caziraghi

      Hello Uwe Klinger

      I enjoyed reading your post. I found it to be very helpful. Now that HANA service is enabled for consumption from Kyma/Kubernetes, how would you bind HANA services? I'm specially interested in how would we control (if possible) properties, like "database_id" for example.

      Best regards,


      Author's profile photo Uwe Klinger
      Uwe Klinger
      Blog Post Author

      Hi Rafael,

      Thanks! If I got you right, your asking how to specify service instance parameters like you can do with the JSON file on Cloud Foundry?

      You can achieve this by adding a parameters object with the service instance specific parameters:

      kind: ServiceInstance
          name: my-hdi
          serviceOfferingName: hana
          servicePlanName: hdi-shared
          externalName: my-hdi
              database_id: your-db-id

      See also BTP service operator doc.

      Author's profile photo Rafael Caziraghi
      Rafael Caziraghi

      That's it. Thank you Uwe!

      Author's profile photo Piotr Tesny
      Piotr Tesny

      Hi Uwe, that's great stuff, thank you so much for sharing the info about bindings and the SAP libraries versions that support it.

      any idea there is no much trace of it in the official SAP documentation?; where did you get the info from? kind regards; Piotr

      Author's profile photo Shifa Tarannum
      Shifa Tarannum

      Hi Uwe Klinger ,

      Thank you for a detailed post. I was facing one issue. We have maintained two different cluster for prod and test. the service bindings are created in prod  and those secrets are copied in test. The issue is that accessing the secrets by proving " SERVICE_BINDING_ROOT" in environment variable works in prod. However this ia not able to access the secret in test cluster. Do you have any idea why is that?