Pattern 1: All-in-One HA Setup¶
This pattern deploys WSO2 API Manager as a highly available active-active cluster with two nodes, each running all components — Control Plane, Gateway, Traffic Manager, and Key Manager. It is suitable for production environments that require high availability and can handle moderate traffic.
How Pattern 1 Differs from Pattern 0¶
| Pattern 0 | Pattern 1 | |
|---|---|---|
| Nodes | 1 | 2 (active-active) |
| Database | Embedded H2 | External database required |
| Docker image | Default WSO2 image | Custom image with JDBC driver required |
| High availability | No | Yes |
Complete these before running helm install
Pattern 1 requires three things that Pattern 0 does not:
- An external database — H2 is not supported. Set up an external database before deploying.
- A custom Docker image — the default WSO2 image does not include JDBC drivers. Build and push a custom image before deploying.
- Database schema initialised — 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 kubectlKubernetes CLI for managing cluster resources 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 and check their versions:
kubectl version --client helm version docker infoVersion Compatibility
Ensure your tool versions are within the supported ranges listed in the Prerequisites page before proceeding.
Step 2 — Verify Your Cluster is Running¶
-
Ensure your Kubernetes cluster is up and running:
kubectl cluster-info kubectl get nodesAll nodes should show a
Readystatus.
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 — Install a Routing Controller¶
Install the NGINX Ingress Controller into your cluster:
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace \
--set controller.service.externalTrafficPolicy=Local
Note
externalTrafficPolicy=Local is required on managed Kubernetes services. Without it, the cloud load balancer health probes fail and traffic never reaches the ingress controller.
Verify the controller is running:
kubectl get pods -n ingress-nginx
The NGINX pod should show 1/1 Running before proceeding.
Step 5 — Build and Push a Custom Docker Image¶
WSO2 API Manager needs to connect to an external database at runtime, but the default WSO2 Docker image does not include third-party JDBC drivers. You must build a custom image that adds the appropriate driver for your database.
Any other customisations — additional JARs, patches, or environment-specific libraries — can also be baked into the image at this stage rather than mounted at deployment time.
Choosing a base image
- DockerHub (
wso2/wso2am:4.6.0) — packages the GA release. Suitable for evaluation and development. - WSO2 Private Registry (
docker.wso2.com/wso2am:4.6.0.0) — includes WSO2 Updates and is recommended for production. Requires an active WSO2 Subscription.
-
Create a directory for the custom image:
mkdir wso2am-custom && cd wso2am-custom -
Create a file named
Dockerfilewith the following content. The example below adds the MySQL JDBC driver — adjust the URL for other databases:FROM wso2/wso2am:4.6.0 ADD --chown=wso2carbon:wso2 \ https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.28/mysql-connector-java-8.0.28.jar \ /home/wso2carbon/wso2am-4.6.0/repository/components/lib/ -
Build the image, replacing
<CONTAINER_REGISTRY>,<IMAGE_REPO>, and<TAG>with your values:docker buildx build --platform linux/amd64 -t <CONTAINER_REGISTRY>/<IMAGE_REPO>:<TAG> .Matching your cluster architecture
The
--platformflag ensures the image is built for the architecture your cluster nodes run on. Most managed clusters (AKS, GKE) and Linux servers uselinux/amd64. If you are building on Apple Silicon (M1/M2/M3/M4) without this flag, the image will be built forlinux/arm64and the pod will fail to start withno match for platform in manifest.To check your cluster node architecture before building:
kubectl get nodes -o jsonpath='{.items[*].status.nodeInfo.architecture}' -
Push the image to your container registry:
docker push <CONTAINER_REGISTRY>/<IMAGE_REPO>:<TAG> -
Get the image digest — you will need it when configuring
values.yaml:docker inspect <CONTAINER_REGISTRY>/<IMAGE_REPO>:<TAG> \ --format='{{index .RepoDigests 0}}'The digest will look like
docker.io/<your-org>/<image>@sha256:abcdef....
Step 6 — Set Up the Database¶
Pattern 1 requires two databases: apim_db and shared_db. Both must be reachable from inside the Kubernetes cluster before the pods start.
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
Note
The JDBC driver for your database is already included in the custom Docker image you built in Step 5. You do not need to follow the JDBC driver steps in the VM-oriented sections of that guide.
Once the scripts have been run, verify that both databases are set up correctly before proceeding:
- Connect to your database instance and confirm that
apim_dbandshared_dbboth exist - Check that tables have been created in each database (the
shared_dbscript createsUM_*andREG_*tables; theapim_dbscript createsAM_*tables)
Step 7 — 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 — you will see a MountVolume.SetUp failed: secret "apim-keystore-secret" not found error.
-
Create the
wso2namespace:kubectl create namespace wso2 -
WSO2 API Manager ships with default keystores inside the Docker image. Extract them and create the secret:
mkdir -p keystores docker run --rm -v "$(pwd)/keystores:/keystores" --entrypoint bash <CONTAINER_REGISTRY>/<IMAGE_REPO>:<TAG> -c "cp /home/wso2carbon/wso2am-4.6.0/repository/resources/security/wso2carbon.jks /home/wso2carbon/wso2am-4.6.0/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 wso2 -
Verify the secret was created:
kubectl get secret apim-keystore-secret -n wso2
Note
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 8 — Deploy WSO2 API Manager¶
Pattern 1 uses a single Helm chart release with two pod replicas forming the active-active cluster. Before deploying, you must configure your custom image and database connection in a values.yaml file.
-
Download the default values file as a starting point:
curl -L https://raw.githubusercontent.com/wso2/helm-apim/4.6.x/docs/am-pattern-1-all-in-one-HA/default_values.yaml \ -o values.yaml -
Open
values.yamland update the two sections below before deploying.Custom image — point to the image you built and pushed in Step 5. The
registryandrepositorytogether form the full image name. For Docker Hub,registryisdocker.ioandrepositoryis<your-username>/<image-name>.Set both
tag(the tag you used indocker build) anddigest(fromdocker inspectin Step 5):wso2: deployment: image: registry: "docker.io" # your container registry repository: "<your-username>/wso2am-mysql" # your Docker Hub username + image name tag: "<TAG>" # tag used in docker build digest: "sha256:abcdef..." # sha256 digest from docker inspect in Step 5Database connection — point to the database you set up in Step 6:
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>"Replace
<JDBC_URL_FOR_APIM_DB>and<JDBC_URL_FOR_SHARED_DB>with the JDBC connection URL for your database. For URL formats per database type, see Setting Up Databases. -
Deploy WSO2 API Manager:
helm install apim wso2/wso2am-all-in-one \ --version 4.6.0-1 \ --namespace wso2 --create-namespace \ --dependency-update \ -f values.yaml -
Wait for both pods to be ready:
kubectl get pods -n wso2 -wBoth pods should show
1/1 Runningbefore proceeding.
Step 9 — Configure Local DNS¶
-
Run the following command in a separate terminal and keep it running:
minikube tunnelNote
minikube tunnelrequires sudo privileges to expose ports 80 and 443. You will be prompted for your system password. Once entered, the tunnel will stay running silently — this is expected. Do not close this terminal. Open a new terminal for the next steps. -
Get the external IP assigned to the ingress:
kubectl get ing -n wso2The ADDRESS column should now show
127.0.0.1. -
Add the following entry to your
/etc/hostsfile:127.0.0.1 am.wso2.com gw.wso2.com websocket.wso2.com websub.wso2.com
-
Get the external IP assigned to the ingress:
kubectl get ing -n wso2 -
Add the following entry to your
/etc/hostsfile, replacing<EXTERNAL-IP>with the value from the output above:<EXTERNAL-IP> am.wso2.com gw.wso2.com websocket.wso2.com websub.wso2.com
-
Get the external IP assigned to the ingress:
kubectl get ing -n wso2 -
For quick testing, add the
ADDRESSvalue to your/etc/hosts:<EXTERNAL-IP> am.wso2.com gw.wso2.com websocket.wso2.com websub.wso2.comFor a production setup, create a DNS record in your DNS provider (e.g. Route 53, Azure DNS, Cloud DNS) mapping the hostnames to the external IP instead of using
/etc/hosts.
Note
These are the default hostnames. If you customised ingress.controlPlane.hostname, ingress.gateway.hostname, ingress.websocket.hostname, or ingress.websub.hostname in your values.yaml, use those values here instead.
Step 10 — Access the Portals¶
Once DNS is configured, open the following URLs in your browser.
| Portal | URL |
|---|---|
| Publisher | https://<kubernetes.ingress.management.hostname>/publisher |
| Developer Portal | https://<kubernetes.ingress.management.hostname>/devportal |
| Carbon Console | https://<kubernetes.ingress.management.hostname>/carbon |
| Gateway | https://<kubernetes.ingress.gateway.hostname> |
Chrome may block access
Chrome enforces HSTS preloading for *.wso2.com domains, which removes the option to bypass the self-signed certificate warning entirely. Use Firefox or Safari instead, and click through the certificate warning when prompted.
Replace the hostname placeholders with the actual values from your values.yaml. Default credentials: admin / admin
Additional Configuration¶
All configurations in this section are made by editing your values.yaml file. Once all changes are in place, deploy using the command in Section 6.
The Helm charts for WSO2 API Manager are available in the WSO2 Helm Chart Repository.
Resource Naming Convention
Kubernetes resources created by the Helm charts follow this naming pattern:
<RELEASE_NAME>-<CHART_NAME>-<RESOURCE_NAME>
1. Image and Registry¶
1.1 Private Registry Authentication
The image registry and repository are configured in Step 8. If your registry is private and requires authentication, enable imagePullSecrets:
wso2:
deployment:
image:
imagePullSecrets:
enabled: true
username: ""
password: ""
2. Database and Credentials¶
2.1 Configure Admin Credentials
The default admin credentials are admin/admin. Change these before deploying to any shared or production environment.
wso2:
apim:
configurations:
adminUsername: ""
adminPassword: ""
2.2 Update Keystore Passwords
If you are mounting custom keystores (see section 3.1), update the passwords here to match.
wso2:
apim:
configurations:
security:
keystores:
primary:
password: ""
keyPassword: ""
internal:
password: ""
keyPassword: ""
tls:
password: ""
keyPassword: ""
truststore:
password: ""
Note
keyPassword must equal password for each keystore. WSO2 API Manager requires these to be identical due to a limitation in internal third-party components — setting them to different values will cause startup failures.
2.3 Configure the Internal Encryption Key
In a distributed or HA deployment, all API Manager nodes must use the same internal encryption key to encrypt and decrypt shared data. Set this before the first startup — changing it afterwards will cause decryption failures for any data already encrypted.
-
Generate a unique 256-bit key:
openssl rand -hex 32 -
Add the key to all your values files:
wso2: apim: configurations: encryption: key: "<generated-64-char-hex-key>"
Warning
All nodes in the deployment must use the exact same key. A mismatch will cause decryption failures across the cluster.
2.4 Component Configuration References
3. Security¶
3.1 Mount Keystore and Truststore
In Step 7 of the Quick Start, you created apim-keystore-secret using the default WSO2 keystores extracted from the Docker image. Those are self-signed certificates suitable for evaluation only.
For production-level keystore setup, refer to Configuring Keystores in WSO2 API Manager. Then recreate the secret with your own certificates:
kubectl create secret generic apim-keystore-secret \
--from-file=wso2carbon.jks \
--from-file=client-truststore.jks \
-n wso2
Keep the following in mind:
- The secret must be created in the same namespace as the deployment (e.g.
wso2). - Use the same secret name in both the
kubectlcommand above and in yourvalues.yaml. - If you are using different keystore filenames or aliases, update the helm chart configurations accordingly.
- You can also include keystores for HTTPS transport.
For more details on configuring keystores, see Configuring Keystores in WSO2 API Manager.
3.2 Encrypt Secrets
By default, database passwords and other sensitive values are stored as plain text in the values files. This is acceptable for local testing but a security risk in production. Use apictl to encrypt these values before deploying.
-
Initialize
apictlusing the trust store:apictl secret initExample:
apictl secret init Enter Key Store location: /home/wso2carbon/wso2am-4.6.0/repository/resources/security/wso2carbon.jks Enter Key Store password: Enter Key alias: wso2carbon Enter Key password: Key Store initialization completed -
Encrypt each of the following values using
apictl secret create:admin_passwordkeystore_passwordkeystore_key_passwordssl_keystore_passwordssl_key_passwordinternal_keystore_passwordinternal_keystore_key_passwordtruststore_passwordapim_db_passwordshared_db_password
Example:
apictl secret create Enter plain alias for secret: db_password Enter plain text secret: Repeat plain text secret: db_password : eKALmLVA+HFVl7vxxxxxxxxxxxxxxxxxxxxxxxxxxxjakhHN -
Replace the plain text values in your values files with the encrypted values.
-
Enable secure vault:
# -- Secure vault enabled secureVaultEnabled: true -
If you are using a cloud provider secret manager, enable it and reference the internal keystore password:
aws: # -- If AWS is used as the cloud provider enabled: true internalKeystorePassword: # -- Secret name in the cloud provider's secret manager secretName: "" # -- Secret key in the cloud provider's secret manager secretKey: ""Note
Currently, AWS, Azure, and GCP Secrets Managers are supported.
3.3 Configure SSL
For WSO2 recommended SSL best practices when exposing services outside the cluster, refer to the WSO2 container guide.
3.4 Configure JWKS URL
Important for multi-pod deployments
In Pattern 1, two pods share the same hostname am.wso2.com. Using localhost for the JWKS URL will only resolve to the pod handling the request, causing token verification failures on the other pod. Use the Kubernetes service name instead so both pods can resolve it correctly:
wso2:
apim:
configurations:
oauth_config:
oauth2JWKSUrl: "https://<service-name>:9443/oauth2/jwks"
4. Routing Controller¶
4.1 Configure NGINX Ingress Controller
Configure Ingress Annotations
Customise these if you want to enable sticky sessions, change the backend protocol, or apply rate limiting.
ingressClass: "nginx"
ingress:
tlsSecret: ""
ratelimit:
enabled: false
zoneName: ""
burstLimit: ""
controlPlane:
hostname: "am.wso2.com"
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "route"
nginx.ingress.kubernetes.io/session-cookie-hash: "sha1"
Refer to the NGINX ingress annotations documentation for the full list of supported options.
Configure TLS for Ingress
kubectl create secret tls my-tls-secret \
--key <private-key-file> \
--cert <certificate-file> \
-n wso2
Then set the secret name in your values.yaml under ingress.tlsSecret. Refer to the Kubernetes ingress TLS documentation for more details.
5. Gateway, High Availability, and User Management¶
5.1 Configure Multiple Gateways
gateway:
environments:
- name: "Default"
type: "hybrid"
gatewayType: "Regular"
provider: "wso2"
displayInApiConsole: true
description: "Handles both production and sandbox token traffic."
showAsTokenEndpointUrl: true
serviceName: "apim-gw-wso2am-gateway-service"
servicePort: 9443
wsHostname: "websocket.wso2.com"
httpHostname: "gw.wso2.com"
websubHostname: "websub.wso2.com"
- name: "Default_apk"
type: "hybrid"
gatewayType: "APK"
provider: "wso2"
displayInApiConsole: true
description: "Handles both production and sandbox token traffic."
showAsTokenEndpointUrl: true
serviceName: "apim-gw-wso2am-gateway-service"
servicePort: 9443
wsHostname: "websocket.wso2.com"
httpHostname: "default.gw.wso2.com:9095"
websubHostname: "websub.wso2.com"
See Deploy through multiple API Gateways for more details.
5.2 Configure User Store Properties
userStore:
type: "database_unique_id"
properties:
ReadGroups: true
Warning
If you do not need to set any custom properties, remove the properties block entirely. An empty properties block will cause the deployment to fail.
See Working with user store properties for the full list of options.
5.3 Configure High Availability
High availability is enabled by default in Pattern 1, which deploys two pods. For local testing where resources are limited, you can disable it to run a single pod:
wso2:
deployment:
highAvailability: false
6. Deploy with Custom Values¶
Once your values.yaml is configured, deploy with:
helm install <release-name> <helm-chart-path> \
--version 4.6.0-1 \
--namespace <namespace> --create-namespace \
--dependency-update \
-f values.yaml
Deployment Parameters
<release-name>— Name for your Helm release (e.g.apim-1)<namespace>— Kubernetes namespace to deploy into (e.g.wso2)<helm-chart-path>— Path to the Helm chart, either the repository chart (wso2/wso2am-all-in-one) or a local clone (e.g../all-in-one)
