From Cloud Foundry to Kyma on SAP BTP: 5 Essential Migration Patterns
Introduction
This blog covers five core patterns you’ll need when moving from CF to Kyma, with working examples you can test on a BTP Trial Kyma cluster. Each pattern is presented as a direct comparison: here’s how you did it in Cloud Foundry, here’s how you do it in Kyma. The code examples are complete and tested—you can copy them into your own Kyma environment and see them work. By the end, you’ll have a practical understanding of the migration path and reference code you can adapt for your own applications.
What you’ll learn:
Basic deployment with APIRules (Istio-based routing)Service bindings using Kubernetes-native patternsPre-runtime configuration with init containersCredential Store integration for secrets managementDestination Service integration from Kyma
Prerequisites
Before starting, you’ll need:
SAP BTP Trial account with Kyma environment enabledkubectl CLI installed locallyBasic familiarity with Kubernetes concepts
Setup: Download Your Kubeconfig
First, let’s get connected to your Kyma cluster:
# Create directory structure
mkdir -p ~/kyma-tutorials
cd ~/kyma-tutorials
# Download kubeconfig from BTP Cockpit
# Navigate to: Subaccount → Kyma Environment → Download Kubeconfig
# Save to ~/.kube/config-kyma-trial
# Set kubeconfig
export KUBECONFIG=~/.kube/config-kyma-trial
# Verify connection
kubectl cluster-info
Critical first step: Enable Istio sidecar injection on your namespace. Kyma requires this for external routing to work:
# Create namespace
kubectl create namespace demo-app
# Enable Istio injection
kubectl label namespace demo-app istio-injection=enabled
# Set as default
kubectl config set-context –current –namespace=demo-app
Pattern 1: Deployment and Routing with APIRules
CF Pattern:
cf push myapp
Kyma Pattern: In Kyma, you need three resources: Deployment, Service, and APIRule.
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-kyma
namespace: demo-app
spec:
replicas: 1
selector:
matchLabels:
app: hello-kyma
template:
metadata:
labels:
app: hello-kyma
spec:
containers:
– name: hello
image: hashicorp/http-echo:latest
args:
– “-text=Hello from Kyma!”
ports:
– containerPort: 5678
—
apiVersion: v1
kind: Service
metadata:
name: hello-kyma-service
namespace: demo-app
spec:
selector:
app: hello-kyma
ports:
– protocol: TCP
port: 80
targetPort: 5678
—
apiVersion: gateway.kyma-project.io/v2alpha1
kind: APIRule
metadata:
name: hello-kyma-api
namespace: demo-app
spec:
gateway: kyma-system/kyma-gateway
hosts:
– hello-kyma.<YOUR_CLUSTER_DOMAIN>
service:
name: hello-kyma-service
port: 80
rules:
– path: /*
methods: [“GET”]
noAuth: true
Deploy it:
kubectl apply -f deployment.yaml
# Wait for pod with Istio sidecar
kubectl get pods -n demo-app
# You should see 2/2 containers (app + istio-proxy)
# Test your app
curl https://hello-kyma.<YOUR_CLUSTER_DOMAIN>
Key differences:
CF Router → Istio + APIRuleCF routes → APIRule hostsAutomatic SSL in both, but APIRule uses noAuth: true vs App Router patterns
Pattern 2: Service Bindings – From VCAP_SERVICES to Kubernetes Secrets
CF Pattern:
cf create-service xsuaa application myxsuaa
cf bind-service myapp myxsuaa
# Credentials appear in VCAP_SERVICES environment variable
Kyma Pattern: In Kyma, service bindings create Kubernetes Secrets.
apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: kyma-xsuaa
namespace: demo-app
spec:
serviceOfferingName: xsuaa
servicePlanName: application
parameters:
xsappname: kyma-demo-xsuaa
tenant-mode: dedicated
scopes:
– name: “$XSAPPNAME.Read”
description: “Read permission”
role-templates:
– name: Reader
scope-references:
– “$XSAPPNAME.Read”
—
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: kyma-xsuaa-binding
namespace: demo-app
spec:
serviceInstanceName: kyma-xsuaa
secretName: kyma-xsuaa-secret
Deploy it:
kubectl apply -f xsuaa-instance.yaml
# Wait for ready (takes 1-2 minutes)
kubectl get serviceinstance kyma-xsuaa -n demo-app -w
Now use the credentials in your app – you have two options:
Option 1: Environment Variables
env:
– name: XSUAA_CLIENTID
valueFrom:
secretKeyRef:
name: kyma-xsuaa-secret
key: clientid
– name: XSUAA_CLIENTSECRET
valueFrom:
secretKeyRef:
name: kyma-xsuaa-secret
key: clientsecret
Option 2: Volume Mounts
volumeMounts:
– name: xsuaa-volume
mountPath: /etc/secrets/xsuaa
readOnly: true
volumes:
– name: xsuaa-volume
secret:
secretName: kyma-xsuaa-secret
Then read credentials from files:
const fs = require(‘fs’);
const clientId = fs.readFileSync(‘/etc/secrets/xsuaa/clientid’, ‘utf8’);
const clientSecret = fs.readFileSync(‘/etc/secrets/xsuaa/clientsecret’, ‘utf8’);
Key differences:
Cloud Foundry Kyma
cf bind-serviceServiceBinding resourceVCAP_SERVICES JSONKubernetes SecretAuto-injected as env varMount as volume OR env varsApp parses JSONApp reads individual keys
Pattern 3: Pre-Runtime Configuration – From .profile to Init Containers
CF Pattern: Create a .profile script in your app directory:
#!/bin/bash
echo “Decrypting secrets…”
# Runs before app starts, in same container
Kyma Pattern: Use Kubernetes Init Containers that run before your main app:
apiVersion: apps/v1
kind: Deployment
metadata:
name: init-demo
namespace: demo-app
spec:
template:
spec:
# Init container runs FIRST
initContainers:
– name: decrypt-config
image: busybox:latest
command:
– sh
– -c
– |
echo “Init container running…”
# Decrypt secrets, download certs, etc.
cat /encrypted/config.txt | base64 -d > /decrypted/config.txt
echo “Runtime info: $(date)” >> /decrypted/runtime-info.txt
echo “Init complete!”
volumeMounts:
– name: encrypted-volume
mountPath: /encrypted
– name: decrypted-volume
mountPath: /decrypted
# Main app runs AFTER init completes
containers:
– name: app
image: myapp:latest
volumeMounts:
– name: decrypted-volume
mountPath: /decrypted
readOnly: true
volumes:
– name: encrypted-volume
configMap:
name: encrypted-config
– name: decrypted-volume
emptyDir: {}
Key differences:
CF .profile Kyma Init Containers
Shell script onlyAny container imageSame containerSeparate containerSequential onlyMultiple init containers (chained)No resource limitsCPU/memory limits per init
Real-world use cases:
Decrypt Credential Store secretsDownload certificates from external CARun database migrationsGenerate dynamic configurationWait for dependencies to be ready
Pattern 4: Credential Store Integration
In CF, you might fetch credentials in your .profile script. In Kyma, use an init container to fetch from Credential Store and write to a shared volume.
First, create the Credential Store service:
apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: credstore
namespace: demo-app
spec:
serviceOfferingName: credstore
servicePlanName: trial
—
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: credstore-binding
namespace: demo-app
spec:
serviceInstanceName: credstore
secretName: credstore-secret
Then fetch credentials in an init container:
spec:
initContainers:
– name: fetch-credentials
image: curlimages/curl:latest
command:
– sh
– -c
– |
# Get OAuth token
OAUTH_URL=”$(cat /credstore/url | sed ‘s|/api.*||’)/oauth/token”
TOKEN=$(curl -s -X POST “$OAUTH_URL”
-d “grant_type=client_credentials”
-d “client_id=$(cat /credstore/username)”
-d “client_secret=$(cat /credstore/password)”
| jq -r ‘.access_token’)
# Fetch credential
CRED_URL=$(cat /credstore/url)
curl -s “$CRED_URL/password?name=database-password”
-H “Authorization: Bearer $TOKEN”
| jq -r ‘.value’ > /secrets/db-password
volumeMounts:
– name: credstore-creds
mountPath: /credstore
readOnly: true
– name: runtime-secrets
mountPath: /secrets
containers:
– name: app
image: myapp:latest
volumeMounts:
– name: runtime-secrets
mountPath: /secrets
readOnly: true
volumes:
– name: credstore-creds
secret:
secretName: credstore-secret
– name: runtime-secrets
emptyDir: {}
Why this matters:
Credentials NOT stored in Kubernetes Secrets (more secure)Credentials fetched at runtime (can rotate without redeploying)Main app doesn’t need Credential Store SDKInit container separates credential management from app logic
Pattern 5: Destination Service Integration
CF Pattern: In CF, you use the App Router with @sap/approuter package, which handles Destination Service integration automatically.
Kyma Pattern: In Kyma, you need to call the Destination Service API directly from your application.
First, create the destination in BTP Cockpit:
Go to Connectivity → DestinationsCreate destination:Name: backend-apiURL: https://api.example.comAuthentication: NoAuthenticationAdditional Property: forwardAuthToken = true
Then create the Destination service instance:
apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: destination
namespace: demo-app
spec:
serviceOfferingName: destination
servicePlanName: lite
—
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: destination-binding
namespace: demo-app
spec:
serviceInstanceName: destination
secretName: destination-secret
Use it in your Node.js app:
const express = require(‘express’);
const axios = require(‘axios’);
const fs = require(‘fs’);
const app = express();
// Read Destination Service credentials from mounted secret
const destCreds = {
uri: fs.readFileSync(‘/etc/secrets/destination/uri’, ‘utf8’).trim(),
clientid: fs.readFileSync(‘/etc/secrets/destination/clientid’, ‘utf8’).trim(),
clientsecret: fs.readFileSync(‘/etc/secrets/destination/clientsecret’, ‘utf8’).trim(),
url: fs.readFileSync(‘/etc/secrets/destination/url’, ‘utf8’).trim()
};
app.get(‘/call-backend’, async (req, res) => {
try {
// 1. Get OAuth token for Destination Service
const tokenResponse = await axios.post(
`${destCreds.url}/oauth/token`,
new URLSearchParams({
grant_type: ‘client_credentials’,
client_id: destCreds.clientid,
client_secret: destCreds.clientsecret
}),
{ headers: { ‘Content-Type’: ‘application/x-www-form-urlencoded’ } }
);
const token = tokenResponse.data.access_token;
// 2. Get destination configuration
const destResponse = await axios.get(
`${destCreds.uri}/destination-configuration/v1/destinations/backend-api`,
{ headers: { Authorization: `Bearer ${token}` } }
);
const backendUrl = destResponse.data.destinationConfiguration.URL;
// 3. Call backend via destination
const backendResponse = await axios.get(`${backendUrl}/posts/1`);
res.json({
message: ‘Success!’,
data: backendResponse.data
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(8080);
Deploy with the secret mounted:
apiVersion: apps/v1
kind: Deployment
metadata:
name: destination-demo
spec:
template:
spec:
containers:
– name: app
image: node:18-alpine
volumeMounts:
– name: destination-creds
mountPath: /etc/secrets/destination
readOnly: true
volumes:
– name: destination-creds
secret:
secretName: destination-secret
—
apiVersion: gateway.kyma-project.io/v2alpha1
kind: APIRule
metadata:
name: destination-demo-api
spec:
gateway: kyma-system/kyma-gateway
hosts:
– destination-demo.<YOUR_CLUSTER_DOMAIN>
service:
name: destination-demo-service
port: 80
rules:
– path: /*
methods: [“GET”]
noAuth: true
Key differences:
Cloud Foundry Kyma
App Router handles itManual API calls@sap/approuter packageCustom integration codeAuto-configured routesExplicit OAuth + API calls
Summary: CF vs Kyma Migration Checklist
Feature Cloud Foundry Kyma Equivalent
Deploycf pushkubectl apply -f deployment.yamlService Bindingcf bind-serviceServiceBinding resourceCredentialsVCAP_SERVICES JSON env varKubernetes Secret (volume/env)Pre-runtime.profile scriptInit ContainersRoutingCF Router + routesIstio + APIRuleAuthenticationApp RouterAPIRule accessStrategiesNamespace isolationCF Spaces (shared network)K8s Namespaces (network policies)Scalingcf scalekubectl scale or HPA
Key Takeaways
Istio is mandatory: Always enable istio-injection=enabled on namespaces before deploying apps that need external access.
Secrets are files, not JSON: In Kyma, service credentials are individual files in a mounted volume, not a single JSON object.
Init containers are powerful: They’re not just a .profile replacement—they can use any container image, have independent resource limits, and can be chained.
Manual integration required: Unlike CF’s App Router, Kyma requires you to integrate with BTP services (like Destination Service) directly via API calls.
Kubernetes-native: Kyma uses standard Kubernetes patterns. If you know Kubernetes, you know Kyma. If you don’t, learning Kyma teaches you portable Kubernetes skills.
Conclusion
The patterns in this blog give you a foundation to migrate existing CF apps to Kyma. Start with simple apps, master these five patterns, then tackle more complex workloads.
From Cloud Foundry to Kyma on SAP BTP: 5 Essential Migration PatternsIntroductionThis blog covers five core patterns you’ll need when moving from CF to Kyma, with working examples you can test on a BTP Trial Kyma cluster. Each pattern is presented as a direct comparison: here’s how you did it in Cloud Foundry, here’s how you do it in Kyma. The code examples are complete and tested—you can copy them into your own Kyma environment and see them work. By the end, you’ll have a practical understanding of the migration path and reference code you can adapt for your own applications.What you’ll learn:Basic deployment with APIRules (Istio-based routing)Service bindings using Kubernetes-native patternsPre-runtime configuration with init containersCredential Store integration for secrets managementDestination Service integration from KymaPrerequisitesBefore starting, you’ll need:SAP BTP Trial account with Kyma environment enabledkubectl CLI installed locallyBasic familiarity with Kubernetes conceptsSetup: Download Your KubeconfigFirst, let’s get connected to your Kyma cluster:# Create directory structure
mkdir -p ~/kyma-tutorials
cd ~/kyma-tutorials
# Download kubeconfig from BTP Cockpit
# Navigate to: Subaccount → Kyma Environment → Download Kubeconfig
# Save to ~/.kube/config-kyma-trial
# Set kubeconfig
export KUBECONFIG=~/.kube/config-kyma-trial
# Verify connection
kubectl cluster-infoCritical first step: Enable Istio sidecar injection on your namespace. Kyma requires this for external routing to work:# Create namespace
kubectl create namespace demo-app
# Enable Istio injection
kubectl label namespace demo-app istio-injection=enabled
# Set as default
kubectl config set-context –current –namespace=demo-appPattern 1: Deployment and Routing with APIRulesCF Pattern:cf push myappKyma Pattern: In Kyma, you need three resources: Deployment, Service, and APIRule.apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-kyma
namespace: demo-app
spec:
replicas: 1
selector:
matchLabels:
app: hello-kyma
template:
metadata:
labels:
app: hello-kyma
spec:
containers:
– name: hello
image: hashicorp/http-echo:latest
args:
– “-text=Hello from Kyma!”
ports:
– containerPort: 5678
—
apiVersion: v1
kind: Service
metadata:
name: hello-kyma-service
namespace: demo-app
spec:
selector:
app: hello-kyma
ports:
– protocol: TCP
port: 80
targetPort: 5678
—
apiVersion: gateway.kyma-project.io/v2alpha1
kind: APIRule
metadata:
name: hello-kyma-api
namespace: demo-app
spec:
gateway: kyma-system/kyma-gateway
hosts:
– hello-kyma.<YOUR_CLUSTER_DOMAIN>
service:
name: hello-kyma-service
port: 80
rules:
– path: /*
methods: [“GET”]
noAuth: trueDeploy it:kubectl apply -f deployment.yaml
# Wait for pod with Istio sidecar
kubectl get pods -n demo-app
# You should see 2/2 containers (app + istio-proxy)
# Test your app
curl https://hello-kyma.<YOUR_CLUSTER_DOMAIN>Key differences:CF Router → Istio + APIRuleCF routes → APIRule hostsAutomatic SSL in both, but APIRule uses noAuth: true vs App Router patternsPattern 2: Service Bindings – From VCAP_SERVICES to Kubernetes SecretsCF Pattern:cf create-service xsuaa application myxsuaa
cf bind-service myapp myxsuaa
# Credentials appear in VCAP_SERVICES environment variableKyma Pattern: In Kyma, service bindings create Kubernetes Secrets.apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: kyma-xsuaa
namespace: demo-app
spec:
serviceOfferingName: xsuaa
servicePlanName: application
parameters:
xsappname: kyma-demo-xsuaa
tenant-mode: dedicated
scopes:
– name: “$XSAPPNAME.Read”
description: “Read permission”
role-templates:
– name: Reader
scope-references:
– “$XSAPPNAME.Read”
—
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: kyma-xsuaa-binding
namespace: demo-app
spec:
serviceInstanceName: kyma-xsuaa
secretName: kyma-xsuaa-secretDeploy it:kubectl apply -f xsuaa-instance.yaml
# Wait for ready (takes 1-2 minutes)
kubectl get serviceinstance kyma-xsuaa -n demo-app -wNow use the credentials in your app – you have two options:Option 1: Environment Variablesenv:
– name: XSUAA_CLIENTID
valueFrom:
secretKeyRef:
name: kyma-xsuaa-secret
key: clientid
– name: XSUAA_CLIENTSECRET
valueFrom:
secretKeyRef:
name: kyma-xsuaa-secret
key: clientsecretOption 2: Volume MountsvolumeMounts:
– name: xsuaa-volume
mountPath: /etc/secrets/xsuaa
readOnly: true
volumes:
– name: xsuaa-volume
secret:
secretName: kyma-xsuaa-secretThen read credentials from files:const fs = require(‘fs’);
const clientId = fs.readFileSync(‘/etc/secrets/xsuaa/clientid’, ‘utf8’);
const clientSecret = fs.readFileSync(‘/etc/secrets/xsuaa/clientsecret’, ‘utf8’);Key differences:Cloud Foundry Kymacf bind-serviceServiceBinding resourceVCAP_SERVICES JSONKubernetes SecretAuto-injected as env varMount as volume OR env varsApp parses JSONApp reads individual keysPattern 3: Pre-Runtime Configuration – From .profile to Init ContainersCF Pattern: Create a .profile script in your app directory:#!/bin/bash
echo “Decrypting secrets…”
# Runs before app starts, in same containerKyma Pattern: Use Kubernetes Init Containers that run before your main app:apiVersion: apps/v1
kind: Deployment
metadata:
name: init-demo
namespace: demo-app
spec:
template:
spec:
# Init container runs FIRST
initContainers:
– name: decrypt-config
image: busybox:latest
command:
– sh
– -c
– |
echo “Init container running…”
# Decrypt secrets, download certs, etc.
cat /encrypted/config.txt | base64 -d > /decrypted/config.txt
echo “Runtime info: $(date)” >> /decrypted/runtime-info.txt
echo “Init complete!”
volumeMounts:
– name: encrypted-volume
mountPath: /encrypted
– name: decrypted-volume
mountPath: /decrypted
# Main app runs AFTER init completes
containers:
– name: app
image: myapp:latest
volumeMounts:
– name: decrypted-volume
mountPath: /decrypted
readOnly: true
volumes:
– name: encrypted-volume
configMap:
name: encrypted-config
– name: decrypted-volume
emptyDir: {}Key differences:CF .profile Kyma Init ContainersShell script onlyAny container imageSame containerSeparate containerSequential onlyMultiple init containers (chained)No resource limitsCPU/memory limits per initReal-world use cases:Decrypt Credential Store secretsDownload certificates from external CARun database migrationsGenerate dynamic configurationWait for dependencies to be readyPattern 4: Credential Store IntegrationIn CF, you might fetch credentials in your .profile script. In Kyma, use an init container to fetch from Credential Store and write to a shared volume.First, create the Credential Store service:apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: credstore
namespace: demo-app
spec:
serviceOfferingName: credstore
servicePlanName: trial
—
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: credstore-binding
namespace: demo-app
spec:
serviceInstanceName: credstore
secretName: credstore-secretThen fetch credentials in an init container:spec:
initContainers:
– name: fetch-credentials
image: curlimages/curl:latest
command:
– sh
– -c
– |
# Get OAuth token
OAUTH_URL=”$(cat /credstore/url | sed ‘s|/api.*||’)/oauth/token”
TOKEN=$(curl -s -X POST “$OAUTH_URL”
-d “grant_type=client_credentials”
-d “client_id=$(cat /credstore/username)”
-d “client_secret=$(cat /credstore/password)”
| jq -r ‘.access_token’)
# Fetch credential
CRED_URL=$(cat /credstore/url)
curl -s “$CRED_URL/password?name=database-password”
-H “Authorization: Bearer $TOKEN”
| jq -r ‘.value’ > /secrets/db-password
volumeMounts:
– name: credstore-creds
mountPath: /credstore
readOnly: true
– name: runtime-secrets
mountPath: /secrets
containers:
– name: app
image: myapp:latest
volumeMounts:
– name: runtime-secrets
mountPath: /secrets
readOnly: true
volumes:
– name: credstore-creds
secret:
secretName: credstore-secret
– name: runtime-secrets
emptyDir: {}Why this matters:Credentials NOT stored in Kubernetes Secrets (more secure)Credentials fetched at runtime (can rotate without redeploying)Main app doesn’t need Credential Store SDKInit container separates credential management from app logicPattern 5: Destination Service IntegrationCF Pattern: In CF, you use the App Router with @sap/approuter package, which handles Destination Service integration automatically.Kyma Pattern: In Kyma, you need to call the Destination Service API directly from your application.First, create the destination in BTP Cockpit:Go to Connectivity → DestinationsCreate destination:Name: backend-apiURL: https://api.example.comAuthentication: NoAuthenticationAdditional Property: forwardAuthToken = trueThen create the Destination service instance:apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: destination
namespace: demo-app
spec:
serviceOfferingName: destination
servicePlanName: lite
—
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: destination-binding
namespace: demo-app
spec:
serviceInstanceName: destination
secretName: destination-secretUse it in your Node.js app:const express = require(‘express’);
const axios = require(‘axios’);
const fs = require(‘fs’);
const app = express();
// Read Destination Service credentials from mounted secret
const destCreds = {
uri: fs.readFileSync(‘/etc/secrets/destination/uri’, ‘utf8’).trim(),
clientid: fs.readFileSync(‘/etc/secrets/destination/clientid’, ‘utf8’).trim(),
clientsecret: fs.readFileSync(‘/etc/secrets/destination/clientsecret’, ‘utf8’).trim(),
url: fs.readFileSync(‘/etc/secrets/destination/url’, ‘utf8’).trim()
};
app.get(‘/call-backend’, async (req, res) => {
try {
// 1. Get OAuth token for Destination Service
const tokenResponse = await axios.post(
`${destCreds.url}/oauth/token`,
new URLSearchParams({
grant_type: ‘client_credentials’,
client_id: destCreds.clientid,
client_secret: destCreds.clientsecret
}),
{ headers: { ‘Content-Type’: ‘application/x-www-form-urlencoded’ } }
);
const token = tokenResponse.data.access_token;
// 2. Get destination configuration
const destResponse = await axios.get(
`${destCreds.uri}/destination-configuration/v1/destinations/backend-api`,
{ headers: { Authorization: `Bearer ${token}` } }
);
const backendUrl = destResponse.data.destinationConfiguration.URL;
// 3. Call backend via destination
const backendResponse = await axios.get(`${backendUrl}/posts/1`);
res.json({
message: ‘Success!’,
data: backendResponse.data
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(8080);Deploy with the secret mounted:apiVersion: apps/v1
kind: Deployment
metadata:
name: destination-demo
spec:
template:
spec:
containers:
– name: app
image: node:18-alpine
volumeMounts:
– name: destination-creds
mountPath: /etc/secrets/destination
readOnly: true
volumes:
– name: destination-creds
secret:
secretName: destination-secret
—
apiVersion: gateway.kyma-project.io/v2alpha1
kind: APIRule
metadata:
name: destination-demo-api
spec:
gateway: kyma-system/kyma-gateway
hosts:
– destination-demo.<YOUR_CLUSTER_DOMAIN>
service:
name: destination-demo-service
port: 80
rules:
– path: /*
methods: [“GET”]
noAuth: trueKey differences:Cloud Foundry KymaApp Router handles itManual API calls@sap/approuter packageCustom integration codeAuto-configured routesExplicit OAuth + API callsSummary: CF vs Kyma Migration ChecklistFeature Cloud Foundry Kyma EquivalentDeploycf pushkubectl apply -f deployment.yamlService Bindingcf bind-serviceServiceBinding resourceCredentialsVCAP_SERVICES JSON env varKubernetes Secret (volume/env)Pre-runtime.profile scriptInit ContainersRoutingCF Router + routesIstio + APIRuleAuthenticationApp RouterAPIRule accessStrategiesNamespace isolationCF Spaces (shared network)K8s Namespaces (network policies)Scalingcf scalekubectl scale or HPAKey TakeawaysIstio is mandatory: Always enable istio-injection=enabled on namespaces before deploying apps that need external access.Secrets are files, not JSON: In Kyma, service credentials are individual files in a mounted volume, not a single JSON object.Init containers are powerful: They’re not just a .profile replacement—they can use any container image, have independent resource limits, and can be chained.Manual integration required: Unlike CF’s App Router, Kyma requires you to integrate with BTP services (like Destination Service) directly via API calls.Kubernetes-native: Kyma uses standard Kubernetes patterns. If you know Kubernetes, you know Kyma. If you don’t, learning Kyma teaches you portable Kubernetes skills.ConclusionThe patterns in this blog give you a foundation to migrate existing CF apps to Kyma. Start with simple apps, master these five patterns, then tackle more complex workloads. Read More Technology Blog Posts by Members articles
#SAP
#SAPTechnologyblog