Installation
This guide covers deploying the X2A Backstage plugin on OpenShift using Red Hat Developer Hub.
Prerequisites
- OpenShift cluster access (CRC or production cluster)
- Cluster-admin rights (for operator installation)
ocCLI tool installed and configured(documentation)- AWS credentials with access to Bedrock (for LLM functionality)
- Ansible Automation Platform instance (optional, for publishing roles)
Quick Start
Deploy to any namespace with these simple commands:
# 1. Git clone current x2a-ansible code
git clone https://github.com/x2ansible/x2ansible/
cd x2a-convertor
# 2. Install operator (cluster-scoped, one-time installation)
oc apply -f deploy/operator.yaml
# 3. Create your namespace (or use existing)
oc create namespace <your-namespace>
# 4. Configure and apply secrets
cp deploy/secrets.yaml.template deploy/secrets.yaml
# 5. Deploy application resources
oc apply -n <your-namespace> -f deploy/app.yaml
# Edit deploy/secrets.yaml with your actual credentials
oc apply -n <your-namespace> -f deploy/secrets.yaml
# 6. Get the application URL
oc get route developer-hub -n <your-namespace> -o jsonpath='https://{.spec.host}{"\n"}'
Installation Files
All deployment files are located in the deploy/ directory at the root of the repository.
1. Operator Installation
File: deploy/operator.yaml
This installs the Red Hat Developer Hub operator. This is cluster-scoped and only needs to be installed once.
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: developer-hub-operator-subscription
namespace: openshift-operators
spec:
channel: "fast-1.9"
installPlanApproval: Automatic
name: rhdh
source: redhat-operators
sourceNamespace: openshift-marketplace
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: devspaces
namespace: openshift-operators
spec:
channel: stable
installPlanApproval: Automatic
name: devspaces
source: redhat-operators
sourceNamespace: openshift-marketplace
Wait for the operator to be ready:
oc get csv -n openshift-operators | grep rhdh
2. Application Deployment
File: deploy/app.yaml
This file contains all the application resources: ConfigMaps, PersistentVolumeClaim, and the Backstage Custom Resource.
All resources intentionally omit the namespace field - specify your desired namespace using the -n flag when applying.
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: x2a-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: x2a-role
rules:
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["create", "get", "list", "watch", "delete"]
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create", "get", "delete", "put"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: x2a-rolebinding
subjects:
- kind: ServiceAccount
name: x2a-sa
roleRef:
kind: Role
name: x2a-role
apiGroup: rbac.authorization.k8s.io
---
# ClusterRole/ClusterRoleBinding for x2a-sa to pass the plugin's connection test
# which hardcodes a pod list call against the 'default' namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: x2a-cluster-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: x2a-cluster-rolebinding
subjects:
- kind: ServiceAccount
name: x2a-sa
namespace: x2ansible
roleRef:
kind: ClusterRole
name: x2a-cluster-role
apiGroup: rbac.authorization.k8s.io
---
# X2A Backstage Plugin Application Resources
# Apply to any namespace using: oc apply -n <your-namespace> -f app.yaml
#
# ConfigMap: Dynamic Plugins Configuration
kind: ConfigMap
apiVersion: v1
metadata:
name: dynamic-plugins
# namespace field intentionally omitted - use -n flag when applying
data:
dynamic-plugins.yaml: |
includes:
- dynamic-plugins.default.yaml
plugins:
# The plugin-x2a-dcr is for RHDH 1.9 only. To be replaced by backstage-plugin-auth in 1.10:
- package: "oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-x2a-dcr:bs_1.49.4__0.2.0"
# We need https://github.com/backstage/backstage/blob/master/plugins/mcp-actions-backend/CHANGELOG.md#0112-next2 or later
# which contains required fix: https://github.com/backstage/backstage/issues/33062
# To match RHDH 1.10 (will be based on Backstage 1.49.4 compatible with mcp-actions-backend:0.1.11), following build is based on mcp-actions-backend:0.1.11 patched for the issues/33062 fix.
# This image works well in both RHDH 1.9 (Backstage 1.45.3) and the upcoming RHDH 1.10.
# We should not need this custom build in a RHDH 1.10 releases (will be with mcp-actions-backend:0.1.12 or later).
- package: "oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/backstage-plugin-mcp-actions-backend:pr_2236__0.1.11"
# All X2A plugins
- package: "oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-x2a:bs_1.49.4__1.2.1"
- package: "oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-x2a-backend:bs_1.49.4__1.4.0"
- package: "oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-x2a-mcp-extras:bs_1.49.4__0.2.0"
- package: "oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-scaffolder-backend-module-x2a:bs_1.49.4__0.3.1"
# In addition, the backstage-plugin-auth-backend 0.25.6 or later is required, should be met since RHDH 1.9. Keeping this note here for a reference in case of issues.
---
# ConfigMap: Application Configuration
kind: ConfigMap
apiVersion: v1
metadata:
name: app-config-rhdh
data:
app-config-rhdh.yaml: |
backend:
cors:
origin:
# for @modelcontextprotocol/inspector, remove if not needed
- http://localhost:6274
credentials: true
actions:
pluginSources:
- 'x2a-mcp-extras'
reading:
allow:
- host: raw.githubusercontent.com
# Disable namespaced tool names for MCP actions
# Workaround for recent MCP server returning `undefined` for tool namespaces
# Will be solved by later @backstage/backend-defaults.
mcpActions:
namespacedToolNames: false
integrations:
github:
- host: github.com
# CUSTOMIZE:
# gitlab:
# - host: gitlab.com
# - host: gitlab.internal.io # Self-hosted GitLab
# apiBaseUrl: https://gitlab.internal.io/api/v4
# bitbucketCloud:
# - host: bitbucket.org
auth:
experimentalDynamicClientRegistration:
# enable the feature
enabled: true
allowedRedirectUriPatterns:
- cursor://*
# CUSTOMIZE:
- https://*
- http://*
# see https://backstage.io/docs/auth/ to learn about auth providers
environment: development
providers:
# See https://backstage.io/docs/auth/guest/provider
# guest: {}
# CUSTOMIZE:
github:
development:
clientId: ${AUTH_GITHUB_CLIENT_ID}
clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
signIn:
resolvers:
- resolver: usernameMatchingUserEntityName
dangerouslyAllowSignInWithoutUserInCatalog: true
# Bitbucket example for authentication
# bitbucket:
# development:
# clientId: ${AUTH_BITBUCKET_CLIENT_ID:-}
# clientSecret: ${AUTH_BITBUCKET_CLIENT_SECRET:-}
# signIn:
# resolvers:
# - resolver: usernameMatchingUserEntityAnnotation
# dangerouslyAllowSignInWithoutUserInCatalog: true
# Gitlab example for authentication
# gitlab:
# development:
# clientId: ${AUTH_GITLAB_CLIENT_ID}
# clientSecret: ${AUTH_GITLAB_CLIENT_SECRET}
# signIn:
# resolvers:
# - resolver: emailMatchingUserEntityProfileEmail
# dangerouslyAllowSignInWithoutUserInCatalog: true
signInPage:
- github
# - gitlab
# - bitbucket
dynamicPlugins:
frontend:
red-hat-developer-hub.backstage-plugin-x2a-dcr:
dynamicRoutes:
- path: /oauth2/*
importName: DcrConsentPage
red-hat-developer-hub.backstage-plugin-x2a:
scaffolderFieldExtensions:
- importName: RepoAuthenticationExtension
appIcons:
- name: x2aIcon
importName: X2AIcon
dynamicRoutes:
- path: /x2a/
importName: X2APage
menuItem:
text: Conversion Hub
icon: x2aIcon
catalog:
locations:
# x2a Create Project template
- type: file
target: /opt/app-root/src/dynamic-plugins-root/red-hat-developer-hub-backstage-plugin-scaffolder-backend-module-x2a/templates/conversion-project-template.yaml
rules:
- allow: [Template]
x2a:
callbackBaseUrl: http://${SERVICE_NAME}.${NAMESPACE}.svc.cluster.local/api/x2a
kubernetes:
image: ${X2A_KUBERNETES_IMAGE:-quay.io/x2ansible/x2a-convertor}
imageTag: ${X2A_KUBERNETES_IMAGE_TAG:-latest}
ttlSecondsAfterFinished: 86400
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2000m
memory: 4Gi
credentials:
llm:
LLM_MODEL: ${LLM_MODEL:-anthropic.claude-v2}
# Example for AWS Bedrock with bearer token:
AWS_REGION: ${AWS_REGION}
AWS_BEARER_TOKEN_BEDROCK: ${AWS_BEARER_TOKEN_BEDROCK}
aap:
url: ${AAP_URL:-https://aap.example.com}
orgName: ${AAP_ORG_NAME:-MyOrganization}
oauthToken: ${AAP_OAUTH_TOKEN:-your-oauth-token}
skipSSLVerification: ${AAP_SKIP_SSL_VERIFICATION:-false}
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: dynamic-plugins-root
spec:
accessModes:
- ReadWriteOnce
# CUSTOMIZATION: Change storage class for your environment
# Common values:
# - OpenShift CRC: crc-csi-hostpath-provisioner
# - AWS EBS: gp2 or gp3
# - Azure: managed-premium
# - GCP: standard or standard-rwo
# List available storage classes: oc get storageclass
# storageClassName: kubevirt-csi-infra-default
storageClassName: crc-csi-hostpath-provisioner
resources:
requests:
storage: 2Gi
---
apiVersion: rhdh.redhat.com/v1alpha4
kind: Backstage
metadata:
name: developer-hub
spec:
deployment:
patch:
spec:
template:
spec:
serviceAccountName: x2a-sa
automountServiceAccountToken: true
containers:
- name: backstage-backend
env:
- name: NODE_EXTRA_CA_CERTS
value: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: SERVICE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.labels['rhdh.redhat.com/app']
application:
dynamicPluginsConfigMapName: dynamic-plugins
appConfig:
mountPath: /opt/app-root/src
configMaps:
- name: app-config-rhdh
extraFiles:
mountPath: /opt/app-root/src
extraEnvs:
secrets:
- name: x2a-credentials
route:
enabled: true
Apply to your namespace:
oc apply -n <your-namespace> -f deploy/app.yaml
3. Secrets Configuration
SECURITY WARNING: Never commit real credentials to git!
File: deploy/secrets.yaml.template
This is a template file with placeholder values. You must create your own secrets.yaml from this template.
# ============================================================================
# WARNING: This is a TEMPLATE file. DO NOT commit actual secrets to git!
# ============================================================================
#
# INSTRUCTIONS:
# 1. Copy this file to secrets.yaml:
# cp secrets.yaml.template secrets.yaml
#
# 2. Edit secrets.yaml with your actual credentials:
# vi secrets.yaml
# (Replace all REPLACE-WITH-YOUR-* placeholders)
#
# 3. Apply to your namespace:
# oc apply -n <your-namespace> -f secrets.yaml
#
# 4. IMPORTANT: The secrets.yaml file is git-ignored and will NOT be committed
#
# PRODUCTION SECURITY NOTES:
# - For production environments, consider using:
# * External Secrets Operator (https://external-secrets.io/)
# * HashiCorp Vault integration
# * OpenShift Sealed Secrets
# * Your organization's secret management solution
#
# - Never commit real credentials to version control
# - Rotate credentials regularly
# - Use minimal permissions for AWS IAM and AAP OAuth tokens
# ============================================================================
---
kind: Secret
apiVersion: v1
metadata:
name: x2a-credentials
type: Opaque
stringData:
# Model to use - see AWS Bedrock documentation for available models
# Examples:
# - anthropic.claude-3-7-sonnet-20250219-v1:0 (recommended)
# - anthropic.claude-3-5-sonnet-20241022-v2:0
LLM_MODEL: "anthropic.claude-3-7-sonnet-20250219-v1:0"
# AWS Region where Bedrock is available
# Common values: us-east-1, us-west-2, eu-west-1
AWS_REGION: "us-east-1"
# AWS IAM Credentials with Bedrock access
# Required permissions: bedrock:InvokeModel
AWS_ACCESS_KEY_ID: "REPLACE-WITH-YOUR-AWS-ACCESS-KEY"
AWS_SECRET_ACCESS_KEY: "REPLACE-WITH-YOUR-AWS-SECRET-KEY"
# ========================================
# Ansible Automation Platform (Optional)
# ========================================
# Only required if:
# - You're using the "publish" feature to push roles to AAP
# - You want to take advantage of deduplication on the migrate phase.
# If not using AAP, you can leave these as placeholders
AAP_URL: "https://your-aap-instance.com"
AAP_ORG_NAME: "your-org-name"
AAP_OAUTH_TOKEN: "REPLACE-WITH-YOUR-AAP-TOKEN"
# GitHub auth:
# Docs: https://x2ansible.github.io/ui/authentication.html#github
# Example Config:
# AUTH_GITHUB_CLIENT_ID: ""
# AUTH_GITHUB_CLIENT_SECRET: ""
# Bitbucket auth:
# Docs: https://x2ansible.github.io/ui/authentication.html#bitbucket
# Example Config:
# AUTH_BITBUCKET_CLIENT_ID: ""
# AUTH_BITBUCKET_CLIENT_SECRET: ""
# Gitlab auth:
# Docs: https://x2ansible.github.io/ui/authentication.html#gitlab
# Example config:
# AUTH_GITLAB_CLIENT_ID: ""
# AUTH_GITLAB_CLIENT_SECRET: ""
Steps to configure secrets:
- Copy the template:
cp deploy/secrets.yaml.template deploy/secrets.yaml - Edit with your credentials:
vi deploy/secrets.yamlReplace all
REPLACE-WITH-YOUR-*placeholders with your actual credentials. - Apply to your namespace:
oc apply -n <your-namespace> -f deploy/secrets.yaml - Restart the Backstage pod to pick up the new secrets:
oc delete pod -n <your-namespace> -l app.kubernetes.io/name=developer-hub
The secrets.yaml file is git-ignored and will not be committed to version control.
For production environments, consider using:
- External Secrets Operator (https://external-secrets.io/)
- HashiCorp Vault integration
- OpenShift Sealed Secrets
- Your organization’s secret management solution
Customization Options
The deployment files include clear comments (marked with # CUSTOMIZATION:) for common customization points.
Namespace Selection
No file editing required - just specify the namespace when applying:
oc apply -n my-custom-namespace -f deploy/app.yaml
Plugin Versions
To use different plugin versions, update the OCI image references in the dynamic-plugins ConfigMap section of deploy/app.yaml. The default manifest also includes the MCP server, X2A MCP extras, and DCR consent UI packages alongside the Conversion Hub plugins.
MCP tools
The default deploy/app.yaml wires up Model Context Protocol (MCP) so assistants can call X2A MCP tools against your RHDH X2A route.
Use MCP tools for the tool list and permissions description.
Optional tweaks in app-config
Most teams can leave the bundled app-config-rhdh fragment as-is. Edit it when you need something different from the sample - for example:
auth.experimentalDynamicClientRegistration- tightenallowedRedirectUriPatternsin production (the sample uses broad patterns suitable for labs).backend.cors- add or remove origins if you use browser-based MCP clients or the Inspector from a host that is not already listed.
YAML examples and behavior notes for those keys live on the MCP tools page.
After any change to deploy/app.yaml, re-apply and restart the RHDH pod so configuration and dynamic plugins reload:
oc apply -n <your-namespace> -f deploy/app.yaml
oc delete pod -n <your-namespace> -l app.kubernetes.io/name=developer-hub
Access the Application
Get the RHDH URL:
oc get route developer-hub -n <your-namespace> -o jsonpath='https://{.spec.host}{"\n"}'
Open the URL in your browser and navigate to the X2A menu item to start using the migration tool.
Troubleshooting
Check operator installation
oc get csv -n openshift-operators | grep rhdh
oc get pods -n openshift-operators
Check Backstage deployment
oc get backstage -n <your-namespace>
oc get pods -n <your-namespace>
oc logs -n <your-namespace> deployment/backstage-developer-hub
Verify secrets are loaded
oc get secret x2a-credentials -n <your-namespace>
oc describe secret x2a-credentials -n <your-namespace>
Common Issues
Issue: Backstage pod fails to start
Solution: Check secrets are properly configured and applied:
oc get secret x2a-credentials -n <your-namespace>
oc logs -n <your-namespace> deployment/backstage-developer-hub