Deploy WSO2 API Manager on OpenShift¶
OpenShift is a Kubernetes distribution with stricter security defaults. The core deployment approach — Helm charts, the same patterns (P0–P5) — is identical to standard Kubernetes. The key difference is that OpenShift ignores the UID defined in the Docker image and injects a random UID at runtime, which requires additional file permission configuration in the image and specific security context settings in the Helm values.
For routing, OpenShift supports Route objects (native to OpenShift), Envoy Gateway (the recommended API Gateway), and standard Kubernetes Ingress (deprecated). The default OpenShift values file uses Routes.
OpenShift deployment requires the following before deploying:
- A custom Docker image — with GID 0 group-write permissions and the JDBC driver for your database. Standard WSO2 images will fail to start on OpenShift.
- An external database — required for distributed deployments (P1–P5). The All-in-One pattern (P0) can use the embedded H2 database for evaluation purposes.
- Database schema initialised — if using an external database, run the WSO2 schema scripts against both databases before the pods start.
Quick Start¶
Step 1 — Install Required Tools¶
-
Ensure the following tools are installed on your machine:
Tool Purpose Install Guide ocOpenShift CLI for managing cluster resources Install kubectlKubernetes CLI (used alongside oc)Install helm(v3)Package manager for deploying WSO2 Helm charts Install dockerRequired to build and push custom WSO2 images Install -
Verify all tools are installed:
oc version helm version docker info
Step 2 — Log in to OpenShift¶
-
Authenticate with the OpenShift CLI:
oc login <API_SERVER_URL> --token=<TOKEN>oc login <API_SERVER_URL> -u <USERNAME> -p <PASSWORD> -
Once authenticated, verify your connection and check your currently selected project:
# Verify connection oc whoami # Check current project oc project
Important
Always create a dedicated project for your deployments instead of using the default to ensure proper resource isolation and security policy enforcement. Using a separate project allows you to manage access control and quotas independently from cluster infrastructure services.
- To create a new project:
oc new-project <PROJECT-NAME>
Creating an OpenShift project automatically creates the corresponding Kubernetes namespace and switches the current context to it.
Step 3 — Add the WSO2 Helm Repository¶
-
Add the WSO2 Helm repository and update it:
helm repo add wso2 https://helm.wso2.com && helm repo update
Step 4 — Build an OpenShift-Compatible Docker Image¶
Standard WSO2 images run as a fixed UID (wso2carbon, UID 802). OpenShift injects a random UID at runtime, so any directories the server writes to must have group-write permissions with root group (GID 0) ownership.
Why GID 0?
OpenShift assigns a random UID to the container process but always uses GID 0 (root group). By granting group-write access to the root group, the container can write to its directories regardless of which UID OpenShift assigns. See Red Hat's guide to OpenShift and UIDs and group ownership and file permission for more detail.
-
Create a
Dockerfilefor the All-in-One image:FROM wso2/wso2am:4.7.0 # Grant root group write access for OpenShift arbitrary UID support USER root RUN chgrp -R 0 /home/wso2carbon && chmod -R g=u /home/wso2carbon USER wso2carbonWSO2 images do not include a JDBC driver. Add the driver for your database:
FROM wso2/wso2am:4.7.0 # Grant root group write access for OpenShift arbitrary UID support USER root RUN chgrp -R 0 /home/wso2carbon && chmod -R g=u /home/wso2carbon USER wso2carbon # Add MySQL JDBC driver — replace URL for other databases ADD --chown=wso2carbon:wso2 \ https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.28/mysql-connector-java-8.0.28.jar \ ${WSO2_SERVER_HOME}/repository/components/lib/ -
Build and push the image, replacing
<REGISTRY>and<TAG>with your values:docker buildx build --platform linux/amd64 -t <REGISTRY>/wso2am-ocp:<TAG> . docker push <REGISTRY>/wso2am-ocp:<TAG>Matching your cluster architecture
The
--platformflag must match the architecture of your cluster nodes:- Managed clusters (AKS, ROSA, ARO) — use
linux/amd64by default - ARM based VMs — use
linux/arm64
Using the wrong platform will cause
ErrImagePullwhen the pod starts. You can check a node's architecture withkubectl get nodes -o wide - Managed clusters (AKS, ROSA, ARO) — use
-
Get the image digest — you will need it when configuring your values file:
docker inspect <REGISTRY>/wso2am-ocp:<TAG> \ --format='{{index .RepoDigests 0}}'
Step 5 — Set Up the Database¶
The All-in-One pattern uses an embedded H2 database by default, which is suitable for evaluation. For production deployments, set up an external database before the pods start.
If you are using H2, skip this step and proceed to Step 6.
For production, follow the Setting Up Databases guide to:
- Set up a database instance accessible from your cluster
- Obtain the schema scripts for your database type
- Run the scripts to initialise both databases
Step 6 — Create the Keystore Secret¶
The Helm chart mounts a Kubernetes secret named apim-keystore-secret as a volume into the pods. The pods will not start if this secret does not exist.
-
Extract the default keystores from your image and create the secret:
mkdir -p keystores docker run --rm -v "$(pwd)/keystores:/keystores" --entrypoint bash \ <REGISTRY>/wso2am-ocp:<TAG> \ -c "cp \${WSO2_SERVER_HOME}/repository/resources/security/wso2carbon.jks \ \${WSO2_SERVER_HOME}/repository/resources/security/client-truststore.jks \ /keystores/" kubectl create secret generic apim-keystore-secret \ --from-file=wso2carbon.jks=keystores/wso2carbon.jks \ --from-file=client-truststore.jks=keystores/client-truststore.jks \ -n apim -
Verify the secret was created:
kubectl get secret apim-keystore-secret -n apim
Note
- Make sure to create the secret inside the namespace which will be used for installing the API Manager.
- The commands above use the default WSO2 keystores which are suitable for evaluation only. For production-level keystore setup, refer to Configuring Keystores in WSO2 API Manager.
Step 7 — Deploy the All-in-One¶
-
Download the OpenShift default values file:
curl -L https://raw.githubusercontent.com/wso2/helm-apim/4.7.x/all-in-one/default_openshift_values.yaml \ -o values.yaml -
Open
values.yamland update the following sections:Custom image — point to the OpenShift-compatible image you built in Step 4:
wso2: deployment: image: registry: "<YOUR_REGISTRY>" repository: "wso2am-ocp" tag: "<YOUR_TAG>" digest: "sha256:..."Database connection:
wso2: apim: configurations: databases: apim_db: url: "<JDBC_URL_FOR_APIM_DB>" username: "<DB_USERNAME>" password: "<DB_PASSWORD>" shared_db: url: "<JDBC_URL_FOR_SHARED_DB>" username: "<DB_USERNAME>" password: "<DB_PASSWORD>"Route hostnames — update the hostnames to match your cluster's DNS:
Prefer Ingress over Routes?
The
default_openshift_values.yamlincludes both akubernetes.routeblock and akubernetes.ingressblock. Routes are enabled by default and ingress is disabled. If you have an NGINX Ingress Controller installed on your OpenShift cluster and prefer to use Ingress, setkubernetes.route.management.enabled,kubernetes.route.gateway.enabled, etc. tofalse, and set the correspondingkubernetes.ingress.*entries totrue.kubernetes: route: management: hostname: "am.example.com" gateway: hostname: "gw.example.com" websocket: hostname: "websocket.example.com" websub: hostname: "websub.example.com" -
Deploy:
helm install apim wso2/wso2am-all-in-one \ --version 4.7.0-1 \ --namespace apim \ --dependency-update \ -f values.yaml \ --set wso2.apim.configurations.encryption.key=$(openssl rand -hex 32)Encryption key is mandatory
WSO2 API Manager 4.7.0 requires a 256-bit encryption key to be set before the first startup. The command above generates one automatically using
openssl. If you are deploying to a shared or production environment, generate the key separately and store it securely — you will need the same key if you redeploy or scale the deployment. For more information, see Configuring Encryption Key. -
Wait for the pod to be ready:
oc get pods -n apim -wThe pod should show
1/1 Runningbefore proceeding.
Step 8 — Verify Routes¶
The Helm chart creates OpenShift Route objects automatically. Verify they were created:
oc get routes -n apim
You should see routes for management, gateway, websocket, and websub with the hostnames you configured.
Step 9 — Configure DNS¶
You need to point your route hostnames to the correct address depending on your environment.
The CRC VM is always reachable at 127.0.0.1 from your local machine. Add the route hostnames to your /etc/hosts file:
sudo sh -c 'echo "127.0.0.1 am.wso2.com gw.wso2.com websocket.wso2.com websub.wso2.com" >> /etc/hosts'
-
Get the router's external IP:
oc get svc router-default -n openshift-ingressNote the
EXTERNAL-IPvalue from the output. -
For quick testing, add the external address to your
/etc/hostsfile:<EXTERNAL-IP> am.wso2.com gw.wso2.com websocket.wso2.com websub.wso2.com
For a production setup, create DNS records in your DNS provider mapping the hostnames to the external IP instead of using /etc/hosts.
Note
These are the default hostnames. If you customised the hostnames in your values.yaml, use those values here instead (kubernetes.route.management.hostname, kubernetes.route.gateway.hostname, etc.).
Step 10 — Access the Portals¶
Once DNS is configured, open the following URLs in your browser:
| Portal | URL |
|---|---|
| Publisher | https://<kubernetes.route.management.hostname>/publisher |
| Developer Portal | https://<kubernetes.route.management.hostname>/devportal |
| Admin Portal | https://<kubernetes.route.management.hostname>/admin |
| Carbon Console | https://<kubernetes.route.management.hostname>/carbon |
| Gateway | https://<kubernetes.route.gateway.hostname> |
Replace the hostname placeholders with the actual values from your values.yaml. With default values, the management hostname is am.wso2.com and the gateway hostname is gw.wso2.com.
Default credentials: admin / admin
Advanced Configuration¶
1. OpenShift Security Context¶
Every component deployed on OpenShift requires the following block in its values file. The default_openshift_values.yaml used in the Quick Start already includes this — you only need to add it manually when creating values files for distributed deployments.
kubernetes:
openshift:
enabled: true
securityContext:
# Allow OpenShift to assign arbitrary UIDs
runAsUser: null
seLinux:
enabled: false
level: ""
seccompProfile:
type: RuntimeDefault
localhostProfile: ""
# AppArmor is not available on OpenShift (which uses SELinux instead)
enableAppArmor: false
configMaps:
scripts:
# Startup scripts mounted via ConfigMap require execute permission
defaultMode: "0457"
| Setting | Why it's needed |
|---|---|
openshift.enabled: true |
Enables OpenShift-specific filesystem adjustments during deployment |
runAsUser: null |
Lets OpenShift assign its random UID instead of the image's fixed UID |
enableAppArmor: false |
AppArmor is not supported on OpenShift |
seLinux.enabled: false |
Leave off unless your cluster has SELinux policies configured for WSO2 |
configMaps.scripts.defaultMode: "0457" |
Startup scripts mounted as a ConfigMap need execute permission |
2. Distributed Deployments¶
For distributed patterns, follow the corresponding Kubernetes pattern guide and apply the OpenShift-specific changes described in this page on top.
| Pattern | Guide |
|---|---|
| Pattern 1 — HA All-in-One | am-pattern-1-all-in-one-ha.md |
| Pattern 2 — All-in-One + Gateway | am-pattern-2-all-in-one-gw.md |
| Pattern 3 — ACP + TM + Gateway | am-pattern-3-acp-tm-gw.md |
| Pattern 4 — ACP + TM + Gateway + KM | am-pattern-4-acp-tm-gw-km.md |
| Pattern 5 — All-in-One + Gateway + KM | am-pattern-5-all-in-one-gw-km.md |
For each component in your chosen pattern:
- Build an OpenShift-compatible image — apply the same
USER root/chgrp/chmod/USER wso2carbonpattern from Step 4 to each component's base image (wso2/wso2am-acp:4.7.0,wso2/wso2am-tm:4.7.0,wso2/wso2am-universal-gw:4.7.0). TM and Gateway do not need the JDBC driver. - Add the security context block from Section 1 to each component's values file.
-
Use the same encryption key across all components — generate it once and pass it to every
helm installcommand:export APIM_ENCRYPTION_KEY=$(openssl rand -hex 32)
Example — Pattern 3 (ACP + TM + Gateway):
Create a values file for each component. For the ACP (values-acp.yaml):
kubernetes:
openshift:
enabled: true
securityContext:
runAsUser: null
seLinux:
enabled: false
level: ""
seccompProfile:
type: RuntimeDefault
localhostProfile: ""
enableAppArmor: false
configMaps:
scripts:
defaultMode: "0457"
wso2:
deployment:
image:
registry: "<YOUR_REGISTRY>"
repository: "<YOUR_ACP_IMAGE>"
tag: "<YOUR_TAG>"
digest: "sha256:..."
apim:
configurations:
databases:
apim_db:
url: "<JDBC_URL_FOR_APIM_DB>"
username: "<DB_USERNAME>"
password: "<DB_PASSWORD>"
shared_db:
url: "<JDBC_URL_FOR_SHARED_DB>"
username: "<DB_USERNAME>"
password: "<DB_PASSWORD>"
Create equivalent values-tm.yaml and values-gw.yaml with the security context block and the custom image reference — TM and GW do not require database configuration.
Deploy in order:
export APIM_ENCRYPTION_KEY=$(openssl rand -hex 32)
helm install apim-acp wso2/wso2am-acp \
--version 4.7.0-1 \
--namespace apim --create-namespace \
--dependency-update \
-f values-acp.yaml \
--set wso2.apim.configurations.encryption.key=$APIM_ENCRYPTION_KEY
helm install apim-tm wso2/wso2am-tm \
--version 4.7.0-1 \
--namespace apim \
--dependency-update \
-f values-tm.yaml \
--set wso2.apim.configurations.encryption.key=$APIM_ENCRYPTION_KEY
helm install apim-gw wso2/wso2am-universal-gw \
--version 4.7.0-1 \
--namespace apim \
--dependency-update \
-f values-gw.yaml \
--set wso2.apim.configurations.encryption.key=$APIM_ENCRYPTION_KEY
Troubleshooting¶
Permission denied errors¶
Symptom: Pod fails to start with Permission denied in the logs.
Cause: The container process cannot write to a directory because the image was not prepared with GID 0 group ownership.
Fix: Ensure your Dockerfile includes the chgrp -R 0 and chmod -R g=u steps from Step 4. Check the pod logs and the applied security context:
oc logs <pod-name> -n apim
oc get pod <pod-name> -n apim -o yaml | grep -A 10 securityContext
Image pull errors¶
Symptom: Pod stuck in ImagePullBackOff or ErrImagePull.
Fix: Create an image pull secret and link it to the service account:
oc create secret docker-registry registry-credentials \
--docker-server=<YOUR_REGISTRY> \
--docker-username=<USERNAME> \
--docker-password=<PASSWORD> \
-n apim
oc secrets link default registry-credentials --for=pull -n apim
Volume mount errors¶
Symptom: Pod crashes with volume-related errors.
Fix: Check that PersistentVolumeClaims are bound:
oc get pvc -n apim
If a volume fails due to fsGroup permissions, patch the deployment:
oc patch deployment <deployment-name> -n apim \
-p '{"spec":{"template":{"spec":{"securityContext":{"fsGroup":0}}}}}'
Inter-component communication failures¶
Symptom: Components cannot reach each other (e.g. GW cannot connect to ACP).
Cause: OpenShift may enforce network policies that block cross-pod traffic within the namespace.
Fix: Check for blocking policies and apply a permissive one for all APIM pods:
oc get netpol -n apim
oc apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-apim-internal
namespace: apim
spec:
podSelector: {}
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: apim
EOF
This allows all pods within the apim namespace to communicate with each other.