This guide explains how to deploy the Keycloak MCP Server container to OpenShift using various methods.
oc CLI installed and configured** NEW: The MCP Server uses JWT Bearer tokens - each user authenticates with their OWN credentials!**
See authentication.md for complete authentication guide
How it works:
User → Gets JWT token from Keycloak
↓
User → MCP Server (with JWT in headers)
↓
MCP Server → Validates token (OIDC)
↓
MCP Server → Keycloak API (with user's token)
↓
Keycloak → Enforces user's permissions
Benefits:
Getting your token:
./scripts/get-mcp-token.sh \
--keycloak-url https://keycloak.example.com \
--username your-username \
--password your-password
** Production Keycloak always uses HTTPS.** Before deploying, configure TLS trust:
See keycloak-tls-setup.md for complete TLS configuration guide
Quick summary:
Reference: Keycloak Operator Installation Tutorial
# 1. Login to OpenShift
oc login --server=https://your-openshift-cluster:6443
# 2. Create a new project
oc new-project keycloak-mcp
# 3. Deploy using manifests
oc apply -f deploy/openshift/
# 4. Get the route URL
oc get route keycloak-mcp-server -o jsonpath='{.spec.host}'
# Deploy with kustomize
oc apply -k deploy/openshift/
# Check deployment status
oc get pods -l app=keycloak-mcp-server
quay.io/sshaaf/keycloak-mcp-server:latest# Create a dedicated project
oc new-project keycloak-mcp
# Or use existing project
oc project keycloak-mcp
** Important: Use HTTPS URL for production Keycloak**
Update ConfigMap (deploy/openshift/configmap.yaml):
data:
# Production Keycloak HTTPS URL
keycloak-url: "https://keycloak.rhbk.apps.example.com"
# OR if using internal service (with custom CA)
# keycloak-url: "https://keycloak.keycloak-system.svc.cluster.local"
Note: No secret is required. The MCP server validates JWT tokens using public OIDC discovery.
Previous versions required a secret, but with JWT authentication, no secrets are needed for deployment.
Environment Variables (in deploy/openshift/deployment.yaml):
stringData:
username: "your-keycloak-admin"
password: "your-keycloak-password"
Or create Secret from command line:
oc create secret generic keycloak-mcp-secret \
--from-literal=username=admin \
--from-literal=password='YourSecurePassword123!'
If using self-signed certificates, configure CA trust:
# Extract CA certificate from Keycloak TLS secret
oc get secret example-tls-secret -n rhbk \
-o jsonpath='{.data.tls\.crt}' | base64 -d > keycloak-ca.crt
# Create CA ConfigMap
oc create configmap keycloak-ca-bundle \
--from-file=ca.crt=keycloak-ca.crt
# See keycloak-tls-setup.md for complete configuration
# Apply all resources
oc apply -f deploy/openshift/configmap.yaml
oc apply -f deploy/openshift/deployment.yaml
oc apply -f deploy/openshift/service.yaml
oc apply -f deploy/openshift/route.yaml
Or apply all at once:
oc apply -f deploy/openshift/
# Check pods
oc get pods -l app=keycloak-mcp-server
# Check deployment
oc get deployment keycloak-mcp-server
# Check service
oc get svc keycloak-mcp-server
# Check route
oc get route keycloak-mcp-server
# View logs
oc logs -f deployment/keycloak-mcp-server
# Get the route URL
ROUTE_URL=$(oc get route keycloak-mcp-server -o jsonpath='{.spec.host}')
echo "Application URL: https://${ROUTE_URL}"
# Test SSE endpoint
curl https://${ROUTE_URL}/mcp/sse
Deploy with a specific git commit SHA for production:
# Edit deployment.yaml or use kustomize
oc set image deployment/keycloak-mcp-server \
keycloak-mcp-server=quay.io/sshaaf/keycloak-mcp-server:49ff54e
Create a Helm chart for more flexibility:
# Install using Helm
helm install keycloak-mcp-server ./charts/keycloak-mcp-server \
--set image.tag=0.3.0 \
--set keycloak.url=http://keycloak:8080 \
--set keycloak.username=admin \
--set keycloak.password=admin
Create and process an OpenShift template:
oc process -f deploy/openshift/template.yaml \
-p KEYCLOAK_URL=http://keycloak:8080 \
-p KEYCLOAK_USERNAME=admin \
-p KEYCLOAK_PASSWORD=admin \
| oc apply -f -
The deployment supports the following environment variables:
| Variable | Description | Default | Required |
|---|---|---|---|
KC_URL |
Keycloak server URL | - | Yes |
KC_USER |
Keycloak admin username | - | Yes |
KC_PASSWORD |
Keycloak admin password | - | Yes |
QUARKUS_HTTP_PORT |
HTTP port | 8080 |
No |
QUARKUS_LOG_LEVEL |
Log level | INFO |
No |
Default resource configuration:
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
Adjust for your workload:
oc set resources deployment/keycloak-mcp-server \
--requests=cpu=200m,memory=512Mi \
--limits=cpu=1000m,memory=1Gi
# Scale up
oc scale deployment/keycloak-mcp-server --replicas=3
# Enable autoscaling
oc autoscale deployment/keycloak-mcp-server \
--min=1 --max=5 --cpu-percent=80
The container runs as non-root by default. To enforce:
spec:
template:
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: keycloak-mcp-server
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
For production, use Sealed Secrets or External Secrets Operator:
# Create sealed secret
echo -n 'admin' | \
kubectl create secret generic keycloak-mcp-secret \
--dry-run=client \
-o yaml | \
kubeseal -o yaml > sealed-configmap.yaml
oc apply -f sealed-configmap.yaml
Restrict network access:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: keycloak-mcp-server-netpol
spec:
podSelector:
matchLabels:
app: keycloak-mcp-server
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: openshift-ingress
ports:
- protocol: TCP
port: 8080
egress:
- to:
- namespaceSelector:
matchLabels:
name: keycloak-system
ports:
- protocol: TCP
port: 8080
The deployment includes comprehensive health checks:
/q/health/live/q/health/ready/q/health/started# Stream logs
oc logs -f deployment/keycloak-mcp-server
# Last 100 lines
oc logs --tail=100 deployment/keycloak-mcp-server
# Previous container (if crashed)
oc logs deployment/keycloak-mcp-server --previous
# Check metrics endpoint
oc exec deployment/keycloak-mcp-server -- \
curl http://localhost:8080/q/metrics
# Create ServiceMonitor for Prometheus
cat <<EOF | oc apply -f -
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: keycloak-mcp-server
labels:
app: keycloak-mcp-server
spec:
selector:
matchLabels:
app: keycloak-mcp-server
endpoints:
- port: http
path: /q/metrics
interval: 30s
EOF
# Check pod status
oc get pods -l app=keycloak-mcp-server
# Describe pod for events
oc describe pod -l app=keycloak-mcp-server
# Check logs
oc logs -l app=keycloak-mcp-server
# Test from pod
oc exec deployment/keycloak-mcp-server -- \
curl -v $KC_URL
# Check DNS resolution
oc exec deployment/keycloak-mcp-server -- \
nslookup keycloak.keycloak-system.svc.cluster.local
# Check network policy
oc get networkpolicy
# Check image pull secret (if using private registry)
oc get secret -n keycloak-mcp
# Create image pull secret
oc create secret docker-registry quay-pull-secret \
--docker-server=quay.io \
--docker-username=sshaaf \
--docker-password=<token>
# Link to service account
oc secrets link default quay-pull-secret --for=pull
# Check route
oc get route keycloak-mcp-server
# Check if TLS is configured
oc describe route keycloak-mcp-server
# Test from inside cluster
oc run test --rm -it --image=curlimages/curl -- \
curl http://keycloak-mcp-server/mcp/sse
If Keycloak is outside OpenShift (e.g., managed service, different cluster):
# configmap.yaml
data:
# External Keycloak with trusted CA certificate
keycloak-url: "https://keycloak.example.com"
If using custom CA or self-signed certificate:
oc create configmap keycloak-ca-bundle --from-file=ca.crt=external-keycloak-ca.crt
If using Keycloak Operator (recommended approach from this tutorial):
# Keycloak is deployed with operator in 'rhbk' namespace
# Extract the TLS certificate
oc get secret example-tls-secret -n rhbk \
-o jsonpath='{.data.tls\.crt}' | base64 -d > keycloak-ca.crt
# Create CA bundle in MCP namespace
oc create configmap keycloak-ca-bundle \
--from-file=ca.crt=keycloak-ca.crt \
-n keycloak-mcp
# Create blue deployment
oc apply -f deploy/openshift/ -l version=blue
# Test blue
oc get route keycloak-mcp-server-blue
# Deploy green
oc apply -f deploy/openshift/ -l version=green
# Switch traffic
oc patch route keycloak-mcp-server \
-p '{"spec":{"to":{"name":"keycloak-mcp-server-green"}}}'
Build from source using OpenShift S2I:
oc new-app quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21~https://github.com/sshaaf/keycloak-mcp-server \
--name=keycloak-mcp-server \
--env KC_URL=http://keycloak:8080 \
--env KC_USER=admin \
--env KC_PASSWORD=admin
Create a Tekton pipeline for automated deployment:
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: keycloak-mcp-deploy
spec:
params:
- name: image-tag
description: Image tag to deploy
default: latest
tasks:
- name: deploy
taskRef:
name: openshift-client
params:
- name: SCRIPT
value: |
oc set image deployment/keycloak-mcp-server \
keycloak-mcp-server=quay.io/sshaaf/keycloak-mcp-server:$(params.image-tag)
oc rollout status deployment/keycloak-mcp-server
Deploy using GitOps with ArgoCD:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: keycloak-mcp-server
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/sshaaf/keycloak-mcp-server
targetRevision: main
path: deploy/openshift
destination:
server: https://kubernetes.default.svc
namespace: keycloak-mcp
syncPolicy:
automated:
prune: true
selfHeal: true
# Backup all resources
oc get all,configmap,secret -l app=keycloak-mcp-server -o yaml > backup.yaml
# Backup specific resources
oc get deployment,service,route keycloak-mcp-server -o yaml > resources-backup.yaml
# Restore from backup
oc apply -f backup.yaml
# Delete all resources
oc delete -f deploy/openshift/
# Or delete by label
oc delete all -l app=keycloak-mcp-server
# Delete project
oc delete project keycloak-mcp
Don’t use latest in production:
image: quay.io/sshaaf/keycloak-mcp-server:latest
Use specific git commit SHA or version:
image: quay.io/sshaaf/keycloak-mcp-server:49ff54e
Always set resource limits to prevent resource exhaustion:
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
Include comprehensive health checks for reliability:
livenessProbe: ...
readinessProbe: ...
startupProbe: ...
spec:
replicas: 3
template:
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: keycloak-mcp-server
topologyKey: kubernetes.io/hostname
Quick deployment with pre-configured manifests Secure by default with non-root containers Production-ready with health checks and resource limits HTTPS configured for secure Keycloak communication OpenShift native using Routes and SecurityContextConstraints Flexible supporting multiple deployment methods Scalable with horizontal pod autoscaling support
| File | Purpose |
|---|---|
deployment.yaml |
Main application deployment with TLS support |
service.yaml |
ClusterIP service definition |
route.yaml |
OpenShift route for external access |
configmap.yaml |
Keycloak URL and OIDC client configuration |
ca-configmap.yaml |
CA certificate for self-signed Keycloak TLS |
kustomization.yaml |
Kustomize configuration |
| keycloak-tls-setup.md | Complete TLS/HTTPS configuration guide |
Production Keycloak uses HTTPS only - Configure TLS trust before deploying
Read keycloak-tls-setup.md for TLS configuration
Keycloak Operator: See installation tutorial
Use specific image tags in production (not latest)
Your Keycloak MCP Server is now ready for OpenShift!
For issues or questions:
oc logs -f deployment/keycloak-mcp-server