Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
{
"enabledPlugins": {
"superpowers@claude-plugins-official": true
},

"permissions": {
"allow": [
"WebFetch(domain:raw.githubusercontent.com)"
]
}
}
32 changes: 32 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Documentation review

When reviewing or editing documentation files (`docs/**/*.md`), load the SAP/Kyma
technical writing rules:

https://raw.githubusercontent.com/kyma-project/community/refs/heads/main/docs/guidelines/content-guidelines/agentic.md

Focus on language and mechanics only (Pass 1 and Pass 2 from the loaded rules).
Do not restructure content or change headings. Silently fix typos, grammar, and
formatting issues without asking for confirmation on each change.

# Documentation standards

## Style rules

Load the SAP/Kyma technical writing styleguide as an on-demand skill for all
documentation work:

https://raw.githubusercontent.com/kyma-project/community/refs/heads/main/docs/guidelines/content-guidelines/agentic.md

Apply all passes (formatting, language, terminology, and reconciliation) when
reviewing or writing documentation.

## Documentation structure

This repository has three documentation tiers:
- `docs/api/` — Auto-generated API reference (do not edit directly)
- `docs/concepts/` — Conceptual overviews (nominal headings, no procedures)
- `docs/tutorials/` — Step-by-step guides (gerund headings, ordered lists for steps)

When writing new pages, match the structure and tone of existing pages in the
same tier.
Binary file added docs/assets/app-push-gh-action-summary.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion docs/user/_sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ export default [
{ text: 'Tutorials', link: './tutorials/README', collapsed: true, items: [
{ text: 'Adding and Deleting a Kyma Module Using Kyma CLI', link: './tutorials/01-10-add-delete-modules' },
{ text: 'Setting Your Module to the Managed and Unmanaged State in Kyma CLI', link: './tutorials/01-11-manage-unmanage-modules' },
{ text: 'Running an Application Using the `app push` Command', link: './tutorials/01-20-app-push-command-usage' }
{ text: 'Running an Application Using the `app push` Command', link: './tutorials/01-20-app-push-command-usage' },
{ text: 'Fast Prototyping on SAP BTP Kyma: App Push', link: './tutorials/01-40-fast-prototyping-app-push' }
] },
{ text: 'Commands', link: './gen-docs/README', collapsed: true, items: commandsSidebar }
];
200 changes: 200 additions & 0 deletions docs/user/tutorials/01-40-fast-prototyping-app-push.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# Fast Prototyping in SAP BTP, Kyma Runtime Using App Push

This tutorial shows how to deploy a single-container application to SAP BTP Kyma runtime in one CLI command using `kyma app push`, then evolve it into an automated GitHub Actions CD pipeline. You don't need to hand-craft YAML, set up a container registry, or configure a CI/CD pipeline upfront — just code, deploy, and iterate.

It is a good fit when you have an app in any language supported by [Cloud Native Buildpacks](https://buildpacks.io/) (Java, Node.js, Go, Python, .NET) and want a clear path from local development to a working prototype in the SAP BTP context — all without writing a Dockerfile.

For this example, use a Spring Boot application that exposes a REST API for managing movies, storing data in BTP Object Store.

## Prerequisites

- SAP BTP, Kyma runtime enabled
- [Kyma CLI](https://help.sap.com/docs/btp/sap-business-technology-platform/kyma-cli?locale=en-US#install-kyma-cli) installed
- [kubectl configured to kubeconfig downloaded from SAP BTP, Kyma runtime](https://developers.sap.com/tutorials/cp-kyma-download-cli.html)
- [Git](https://git-scm.com/downloads) installed
- [Add the Istio, API Gateway, and BTP Operator Kyma modules](https://help.sap.com/docs/btp/sap-business-technology-platform/enable-and-disable-kyma-module?locale=en-US#adding-a-kyma-module), if not added by default
- The Docker Registry community module [added](https://kyma-project.io/external-content/community-modules/docs/user/README.html#quick-install)

### Clone the Git Repository

1. Clone the `movies-rest` folder from the [kyma-runtime-samples](https://github.com/SAP-samples/kyma-runtime-samples) repository:

```Shell/Bash
git clone --filter=blob:none --no-checkout https://github.com/SAP-samples/kyma-runtime-samples
cd kyma-runtime-samples
git sparse-checkout init --no-cone
git sparse-checkout set 'movies-rest/'
git checkout
```

### Create the Object Store ServiceInstance and ServiceBinding

1. Create the `dev` namespace:

```Shell/Bash
kubectl create namespace dev
```

2. Create the Object Store ServiceInstance and ServiceBinding:

```Shell/Bash
kubectl -n dev apply -f - <<EOF
apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: object-store-instance
spec:
serviceOfferingName: objectstore
servicePlanName: standard
---
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: object-store-binding
spec:
serviceInstanceName: object-store-instance
EOF
```

3. Wait for the binding to become ready:

```Shell/Bash
kubectl -n dev get servicebinding object-store-binding -w
```

Once the `STATUS` column shows `Ready`, a Kubernetes Secret named `object-store-binding` is created in the namespace with the Object Store credentials.

### Deploy the Application

1. From the `movies-rest` directory, run the following command to push the application:

```Shell/Bash
kyma app push \
--name movies-rest \
--namespace dev \
--code-path . \
--container-port 8080 \
--expose \
--istio-inject=true \
--mount-service-binding-secret object-store-binding \
--env-from-file .env
```

What happens under the hood:
- Source code is built into a container image using [Cloud Native Buildpacks](https://buildpacks.io/) (Paketo). No Dockerfile is required — Buildpacks detect `pom.xml` and automatically build a Java application with the correct JDK.
- The image is pushed to the in-cluster Docker Registry.
- A Deployment, Service, and APIRule are created.
- The Object Store binding secret is mounted at `/bindings/secret-object-store-binding`, and `SERVICE_BINDING_ROOT=/bindings` is set automatically.

> **Note:** The same approach works for any language supported by Cloud Native Buildpacks — Node.js, Go, Python, .NET, and more.

### Verify the Deployment

1. Once `kyma app push` completes, it prints the app URL:

```
The movies-rest app is available under the
movies-rest.<CLUSTER_DOMAIN>.kyma.ondemand.com
```

> **Tip:** In quiet mode, the app URL is the only output — useful for capturing it in scripts:
>
> ```Shell/Bash
> APP_URL=$(kyma app push ... --quiet)
> echo $APP_URL
> ```

2. Open the interactive Swagger UI in your browser at `https://<APP_URL>/swagger-ui.html` and test the CRUD operations on the movies endpoint.

The OpenAPI specification is also available at `https://<APP_URL>/v3/api-docs`.

### (Optional) Automate Deployments with GitHub Actions

Once your prototype stabilizes, you can automate deployments on every push to your repository using GitHub Actions.

1. Push your application code to a GitHub repository, for example `https://github.com/<YOUR-ORG>/movies-rest`.

2. Authorize the repository's GitHub Actions workflows to deploy to your Kyma cluster:

```Shell/Bash
kyma alpha authorize repository \
--client-id my-client-id-for-gh-action \
--cluster-wide \
--clusterrole edit \
--repository <YOUR-ORG>/movies-rest
```

This command configures your Kyma cluster to trust GitHub OIDC tokens issued for the specified repository. The workflow obtains cluster access using a short-lived GitHub OIDC token — no long-lived credentials are stored. The only values you need to keep as secrets are the API server URL and CA certificate, which are connection details rather than credentials.

> **Note:** The `--clusterrole edit` flag is used here for simplicity. In production, choose the most restrictive ClusterRole that satisfies your workflow's needs. You can also limit authorization to a specific workflow, branch, or environment using `--require-claim`. Run `kyma alpha authorize repository --help` for details.

3. Add the cluster connection details as GitHub Actions secrets. Run the following commands locally to get the values:

```Shell/Bash
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'
kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}'
```

4. In your GitHub repository, go to **Settings** > **Secrets and variables** > **Actions** > **New repository secret** and add:
- `SERVER` — the API server URL returned by the first command
- `CA_CRT` — the base64-encoded CA certificate returned by the second command

5. Create the following GitHub Actions workflow file in your repository at `.github/workflows/deploy.yaml`:

```yaml
name: Deploy

permissions:
id-token: write
contents: read

on:
push:
branches:
- main

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Kyma CLI
uses: kyma-project/setup-kyma-cli@v1.1.0

- name: Get kubeconfig
id: oidc
uses: kyma-project/setup-kyma-cli/kubeconfig@v1.1.0
with:
audience: "my-client-id-for-gh-action"
api-server-url: "${{ secrets.SERVER }}"
ca-crt: "${{ secrets.CA_CRT }}"
id-token-auto-refresh: "true"

- name: Set short SHA
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV

- uses: kyma-project/setup-kyma-cli/app-push@v1.1.0
with:
name: movies-rest
namespace: dev
code-path: .
build-tag: "${{ env.SHORT_SHA }}"
container-port: "8080"
expose: "true"
istio-inject: "true"
mount-service-binding-secret: object-store-binding
kubeconfig: "${{ steps.oidc.outputs.kubeconfig }}"
env-from-file: .env
append-output-path: /swagger-ui.html
```

6. Every push to the `main` branch now triggers a fresh build and deploy. No local tooling is required after the initial setup.

![GitHub Actions deploy workflow summary showing a successful deploy job and the application URL](../../assets/app-push-gh-action-summary.png)

## Summary

With `kyma app push` you go from source code to a running, externally accessible application with BTP service bindings in a single command. The same deployment can then be moved into a GitHub Actions workflow with zero code changes — just copy the flags into the action inputs.

This is not a throwaway prototype tool. The Deployment, Service, and APIRule it creates are standard Kubernetes resources you can later manage with Helm, Kustomize, or any GitOps tool.
2 changes: 2 additions & 0 deletions examples/movies-api/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
BPL_JVM_THREAD_COUNT=20
JAVA_TOOL_OPTIONS=-XX:ReservedCodeCacheSize=40M -XX:MaxMetaspaceSize=80M -Xss512k
44 changes: 44 additions & 0 deletions examples/movies-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Movies API

A simple CRUD REST service for managing movies, backed by SAP BTP Object Store. Built with Spring Boot and designed to be deployed to SAP BTP Kyma runtime using `kyma app push`.

## Prerequisites

The service uses SAP BTP Object Store (S3-compatible) for persistence. A Kubernetes Secret named `object-store-binding` containing the Object Store service binding credentials must exist in the same namespace before deployment. See `k8s/service-instance.yaml` and `k8s/service-binding.yaml` for the required resources.

## Deploy

```bash
kyma app push \
--name my-prototype \
--code-path . \
--container-port 8080 \
--expose \
--istio-inject=true \
--mount-service-binding-secret object-store-binding \
--env-from-file .env
```

## API Documentation

Once deployed, the Swagger UI is available at:

```
https://<YOUR-APP-URL>/swagger-ui.html
```

The OpenAPI spec (JSON) is at:

```
https://<YOUR-APP-URL>/v3/api-docs
```

## Endpoints

| Method | Path | Description |
|--------|------|-------------|
| GET | /movies | List all movies |
| GET | /movies/{id} | Get a movie by ID |
| POST | /movies | Create a new movie |
| PUT | /movies/{id} | Update a movie |
| DELETE | /movies/{id} | Delete a movie |
62 changes: 62 additions & 0 deletions examples/movies-api/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
</parent>

<groupId>com.example</groupId>
<artifactId>movies</artifactId>
<version>1.0.0</version>

<properties>
<java.version>21</java.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.sap.cloud.environment.servicebinding</groupId>
<artifactId>java-bom</artifactId>
<version>0.10.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>com.sap.cloud.environment.servicebinding</groupId>
<artifactId>java-sap-service-operator</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.25.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.movies;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@OpenAPIDefinition(info = @Info(
title = "Movies API",
version = "1.0.0",
description = "CRUD REST service for movies, backed by SAP BTP Object Store"))
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Loading