External CI Integration
This guide covers how to integrate external CI platforms with OpenChoreo using the Workload API. Instead of using OpenChoreo's built-in CI (Argo Workflows), you can use your existing CI infrastructure to build container images and trigger deployments.
GitHub Actions and GitLab CI integration support is coming soon. This guide currently covers Jenkins integration.
Overviewβ
OpenChoreo supports two approaches for building components:
- Built-in CI - Argo Workflows managed by OpenChoreo
- External CI - Your own CI platform builds images and creates workloads via API
This guide covers the External CI approach, where:
- Your CI pipeline builds the container image
- Your CI calls the OpenChoreo API to create/update workloads
- OpenChoreo handles deployment, scaling, and observability
Prerequisitesβ
- OpenChoreo installed and running
- Access to your identity provider (Thunder IDP or configured OIDC provider)
- Jenkins configured and accessible
- Container registry accessible to both CI and OpenChoreo cluster
Step 1: Create a Service Accountβ
External CI needs OAuth2 client credentials to obtain JWT tokens for authenticating with the OpenChoreo API.
Using Thunder IDP (Default)β
If you're using the bundled Thunder IDP, create an OAuth2 application through the Thunder Developer Portal.
See Identity Configuration for Thunder access details and default credentials.
1. Create an OAuth2 Applicationβ
- Open the Thunder Developer Portal at
<thunder-url>/develop - Log in with your admin credentials (default:
admin/admin) - Create a new application and select Backend Service (server-to-server APIs) as the application type
- Copy the Client ID and Client Secret and store them securely as Jenkins credentials
2. Verify Token Generationβ
You can test the credentials by exchanging them for an access token:
curl -X POST "<thunder-url>/oauth2/token" \
-d "grant_type=client_credentials" \
-d "client_id=<your-client-id>" \
-d "client_secret=<your-client-secret>"
For long-running CI pipelines, configure a longer token validity period in the application settings within the Thunder Developer Portal.
Using Other Identity Providersβ
If you've configured a different OIDC provider, create a service account following your provider's documentation. The token must include claims that OpenChoreo can validate against your security configuration.
Step 2: Create Component with External CIβ
When creating a new component in Backstage:
- Navigate to Create in Backstage
- Select your component type
- For Deployment Source, choose "External CI"
- Optionally configure Jenkins for build visibility
- Complete the wizard
The component is created without a workload. Your CI pipeline will create workloads when builds complete.
Step 3: Configure Your CI Pipelineβ
Jenkinsβ
Store the following as Jenkins credentials before using this pipeline:
| Credential ID | Type | Description |
|---|---|---|
openchoreo-ci-credentials | Username with password | Client ID (username) and Client Secret (password) from Step 1 |
thunder-url | Secret text | Thunder IDP URL (e.g., https://thunder.example.com) |
openchoreo-api-url | Secret text | OpenChoreo API URL (e.g., https://api.openchoreo.example.com) |
pipeline {
agent any
environment {
NAMESPACE = 'default'
PROJECT = 'my-project'
COMPONENT = 'my-service'
REGISTRY = 'registry.example.com'
}
stages {
stage('Build & Push') {
steps {
script {
env.IMAGE = "${REGISTRY}/${COMPONENT}:${BUILD_NUMBER}"
sh "docker build -t ${env.IMAGE} ."
sh "docker push ${env.IMAGE}"
}
}
}
stage('Deploy to OpenChoreo') {
steps {
withCredentials([
usernamePassword(
credentialsId: 'openchoreo-ci-credentials',
usernameVariable: 'CLIENT_ID',
passwordVariable: 'CLIENT_SECRET'
),
string(credentialsId: 'thunder-url', variable: 'THUNDER_URL'),
string(credentialsId: 'openchoreo-api-url', variable: 'OPENCHOREO_API_URL')
]) {
sh '''
# Get access token
TOKEN=$(curl -sf -X POST "${THUNDER_URL}/oauth2/token" \
-d "grant_type=client_credentials" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" \
| jq -r '.access_token')
# Create/update workload
curl -sf -X POST \
"${OPENCHOREO_API_URL}/api/v1/namespaces/${NAMESPACE}/projects/${PROJECT}/components/${COMPONENT}/workloads" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{\\"containers\\":{\\"main\\":{\\"image\\":\\"${IMAGE}\\"}}}"
'''
}
}
}
}
}
Step 4: Enable Jenkins Visibility in Backstageβ
OpenChoreo Backstage includes a built-in Jenkins plugin that displays build status and history directly in the portal.
Enable Jenkins Plugin (Administrator)β
The Jenkins plugin is always installed. Enabling it requires two things: storing the Jenkins API key in the Backstage secret, and setting the connection details via Helm values.
Store the Jenkins API Keyβ
The API key is read from the jenkins-api-key key in the Backstage credentials secret (referenced by backstage.secretName). Add it the same way other Backstage secrets are managed.
- External Secret (Recommended)
- Direct Secret
If you followed the k3d setup or on your environment guide, the jenkins-api-key is already present in your ClusterSecretStore and ExternalSecret with a placeholder value. Update it in your secret provider with a real Jenkins API token.
For the fake provider used in local dev, edit the ClusterSecretStore and replace the placeholder backstage-jenkins-api-key value with your real Jenkins API token:
kubectl edit clustersecretstore default
Find the backstage-jenkins-api-key entry and update its value:
- key: backstage-jenkins-api-key
value: "your-real-jenkins-api-key"
Then trigger the ExternalSecret to sync the updated value:
kubectl annotate externalsecret backstage-secrets \
-n openchoreo-control-plane \
force-sync=$(date +%s) --overwrite
Restart Backstage to pick up the new secret:
kubectl rollout restart deployment/backstage -n openchoreo-control-plane
If you manage the Backstage secret directly without External Secrets Operator:
kubectl create secret generic backstage-secrets \
-n openchoreo-control-plane \
--from-literal=backend-secret="your-backend-secret" \
--from-literal=client-secret="your-client-secret" \
--from-literal=jenkins-api-key="your-real-jenkins-api-key" \
--dry-run=client -o yaml | kubectl apply -f -
Restart Backstage to pick up the new secret:
kubectl rollout restart deployment/backstage -n openchoreo-control-plane
Enable Jenkins in Helm Valuesβ
Set the Jenkins base URL and username. The API key is picked up from the secret automatically.
backstage:
externalCI:
jenkins:
enabled: true
baseUrl: "https://jenkins.example.com"
username: "admin"
Add Jenkins Annotation to Componentsβ
Once the plugin is enabled, add the Jenkins annotation to your components to display build status.
- Navigate to your component in Backstage
- Click the context menu (...) and select Edit Annotations
- Add the annotation:
jenkins.io/job-full-name=/job/my-org/job/my-service - Click Save
What You'll Seeβ
When configured correctly:
- Overview Page: A Jenkins status card appears showing recent build status
- Jenkins Tab: A dedicated tab shows detailed build history and logs
You can also configure Jenkins annotations during component creation by selecting Jenkins in the wizard.
Troubleshooting Jenkins Visibilityβ
Jenkins tab shows but API calls fail:
- Check
baseUrlandusernameare correct in Helm values - Verify the
jenkins-api-keyvalue in your Backstage secret is a valid Jenkins API token - Ensure Jenkins is accessible from the Kubernetes cluster
- Verify the job path in the annotation matches the actual Jenkins job
Jenkins status cards not showing on Overview:
- Cards only appear when the entity has a Jenkins annotation
- If an external CI annotation is present, it replaces the OpenChoreo Workflows card
- Check browser console for any JavaScript errors
Workload APIβ
The CI pipeline creates workloads using the OpenChoreo REST API:
POST /api/v1/namespaces/{namespace}/projects/{project}/components/{component}/workloads
Authorization: Bearer <token>
Content-Type: application/json
The request body specifies containers, endpoints, and connections for the component. For the full schema and examples, see the Workload API Reference.
Troubleshootingβ
401 Unauthorizedβ
- Token expired: Get a new token using client credentials
- Invalid token: Verify your
client_idandclient_secret - Wrong JWKS: Ensure Thunder/IDP URL is correctly configured in OpenChoreo
404 Not Foundβ
- Component doesn't exist: Create the component first in Backstage
- Wrong path: Verify namespace, project, and component names match exactly
403 Forbiddenβ
- Missing permissions: Service account may lack required roles
- Namespace restriction: Check if service account has access to the target namespace
Connection Refusedβ
- API not accessible: Verify
OPENCHOREO_API_URLis reachable from CI runner - Firewall rules: Ensure CI runners can reach the OpenChoreo API endpoint
Debug Tipsβ
-
Test token generation independently:
curl -v -X POST "$THUNDER_URL/oauth2/token" \
-d "grant_type=client_credentials" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" -
Test API connectivity with a simple GET:
curl -v -H "Authorization: Bearer $TOKEN" \
"$OPENCHOREO_API_URL/api/v1/namespaces" -
Check workload status after creation:
curl -H "Authorization: Bearer $TOKEN" \
"$OPENCHOREO_API_URL/api/v1/namespaces/$NAMESPACE/projects/$PROJECT/components/$COMPONENT/workloads"
Best Practicesβ
- Rotate credentials regularly: Set up credential rotation for service accounts
- Use short-lived tokens: Configure appropriate token validity periods
- Implement retry logic: Handle transient API failures gracefully
- Tag images consistently: Use git SHAs or semantic versions for traceability
- Secure secrets: Use Jenkins Credentials for storing sensitive values
- Monitor deployments: Set up alerts for failed workload creations