11/27/2025

Hands-On Guide: Running WSO2 API Manager + APK Gateway on Minikube





This guide walks you through deploying a fully working API management platform locally using:

  • WSO2 API Manager (APIM)Control Plane

  • WSO2 API Platform for Kubernetes (APK)Kubernetes-native API Gateway

  • Minikube — local Kubernetes cluster

In this tutorial, you'll deploy:

1. apk-wso2-apk-adapter Purpose: The "Bridge" / XDS Server. Details: This component acts as the translator between the Management Plane (API Manager / Control Plane) and the Data Plane (Gateway). It fetches API definitions, subscriptions, and policies from the Control Plane and converts them into Kubernetes Custom Resources (CRs). It also serves as the xDS server, pushing dynamic configurations to the Envoy-based gateway. 2. apk-wso2-apk-gateway-runtime (Envoy + Enforcer) Purpose: The Traffic Handler (Data Plane). Details: This is the core ingress gateway that processes actual API traffic. Envoy: A high-performance proxy that intercepts incoming requests, handles routing, and performs SSL termination. Enforcer: A filter/sidecar that applies "API Management" logic to the traffic. It performs authentication (OAuth2, JWT), authorization, and enforces policies (e.g., rate limiting) before allowing Envoy to forward the request to your backend. Note: In newer APK versions, Enforcer logic is increasingly implemented as native Envoy filters (Golang-based) for performance. 3. apk-wso2-apk-common-controller Purpose: The K8s Resource Reconciler. Details: This is a Kubernetes controller that watches for changes in specific APK Custom Resources (CRDs) across the cluster (or specific namespaces). It creates and manages the lifecycle of dependent child resources required for an API to function, ensuring the cluster state matches the desired state defined in your API CRs. It simplifies multi-namespace API management. 4. apk-wso2-apk-config-ds (Config Domain Service) Purpose: The Configuration Generator. Details: "DS" stands for Domain Service. This component is responsible for generating the actual Kubernetes resources (like HTTPRoute, Service, Gateway, etc.) based on abstract API definitions. When you deploy an API, the Adapter often relies on this service to help generate the complex K8s YAML/JSON structures required to configure the gateway. 5. apk-wso2-apk-idp Purpose: Internal Identity Provider / Key Manager. Details: This acts as a lightweight internal Identity Provider (IDP). It is responsible for minting and validating authentication tokens (like JWTs) for the system's internal use or for testing APIs. In a full production environment, this might be replaced or integrated with an external Identity Provider (like WSO2 Identity Server, Okta, or Keycloak), but apk-idp allows the system to function standalone. 6. redis Purpose: Shared State Store (Rate Limiting & Caching). Details: Redis is used as a fast, in-memory data store for: Rate Limiting: Storing counters to track how many requests a user has made (e.g., "10 requests per minute"). This allows multiple gateway replicas to enforce limits accurately. Token Caching: Storing validated security tokens to reduce the overhead of validating them against the IDP for every single request. 7. cert-manager Purpose: Certificate Lifecycle Management. Details: This is a standard Cloud Native Computing Foundation (CNCF) tool used to automate the management of TLS certificates. In WSO2 APK, it:

  •     Issues certificates for the API Gateways (enabling HTTPS).
  •     Manages internal mTLS certificates for secure communication between APK components.
  •     Automatically renews certificates before they expire.

This post is based on a real, complete setup — not a theoretical guide.


Architecture Summary

  • Control Flow: Developer → Control Plane (APIM) → AdapterCommon Controller / Config DS → K8s API Server.

  • Traffic Flow: User → Cert-Manager (TLS) → Gateway Runtime (Envoy) → Redis (Check Limits) → Backend.



🧰 Prerequisites

  • Minikube (v1.30+)

  • kubectl

  • Docker

  • 4+ CPUs, 8–16GB RAM

  • Linux/macOS/WSL recommended


🟦 Step 1 — Start Minikube

minikube start --cpus=4 --memory=8192


Set namespace:

kubectl create namespace apk

kubectl config set-context --current --namespace=apk



🟩 Step 2 — Enable Ingress

APK requires ingress for hostname-based routing.

minikube addons enable ingress

minikube addons enable metallb

Verify:


$ kubectl get pods -n ingress-nginx

NAME                                       READY   STATUS    RESTARTS   AGE

ingress-nginx-controller-67c5cb88f-vff6x   1/1     Running   0          18h



🟪 Step 3 — Install APIM Control Plane + APK Agent

Again directly from the Quick Start With Control Plane: apk.docs.wso2.com

# Add APK Helm repo

helm repo add wso2apk https://github.com/wso2/apk/releases/download/1.3.0-1


helm repo update


# Install Kubernetes Gateway data plane

helm install apk wso2apk/apk-helm \

  --version 1.3.0-1 \

  -f https://raw.githubusercontent.com/wso2/apk/refs/tags/1.3.0-1/helm-charts/samples/apk/1.3.0-1-cp-enabled-values.yaml \

  -n apk

NAME: apk

LAST DEPLOYED: Thu Nov 27 09:49:57 2025

NAMESPACE: apk

STATUS: deployed

REVISION: 1

TEST SUITE: None

NOTES:

Welcome to the WSO2 API Platform for Kubernetes!


Congratulations. You've successfully deployed WSO2 APK using Helm, you'll need to monitor and manage the deployment to ensure everything is running smoothly.


   - Monitor Pods:

     Check the status of the pods to ensure they are up and running:

     ---

     kubectl get pods

     ---

   

   - Monitor Services:

     Verify that the services are running and find their external IPs to access the APIs:

     ---

     kubectl get services

     ---


For more detailed information, troubleshooting, and advanced configurations, we encourage you to explore the official WSO2 documentation.


- APK Documentation: [https://apk.docs.wso2.com/en/latest/get-started/quick-start-guide/]


This is just the beginning of your APK journey. Feel free to customize and tailor your deployment to match your organization's specific needs.


For any questions or assistance, don't hesitate to reach out to our discord channel.


Happy API management with WSO2 APK!



$ kubectl get pod

NAME                                                         READY   STATUS      RESTARTS        AGE

apim-wso2am-acp-deployment-1-7f868d47b8-hbl7l                1/1     Running     0               4h13m




🟧 Step 4 — Install WSO2 APK Gateway


Add repo:

# Helm repo for the APIM–APK agent

helm repo add wso2apkagent https://github.com/wso2/product-apim-tooling/releases/download/1.3.0

helm repo update


Install APK:

# Install the agent

helm install apim-apk-agent wso2apkagent/apim-apk-agent \

  --version 1.3.0 \

  -f https://raw.githubusercontent.com/wso2/apk/main/helm-charts/samples/apim-apk-agent/1.3.0-values.yaml \

  -n apk


Verify:

kubectl get pods -n apk


Expected components:

  • apk-wso2-apk-adapter

  • apk-wso2-apk-gateway-runtime (Envoy + enforcer)

  • apk-wso2-apk-common-controller

  • apk-wso2-apk-config-ds

  • apk-wso2-apk-idp

  • redis

  • cert-manager

APK gateway is now running.



🟫 Step 5 — Configure Local Hostnames

Find Minikube IP:

minikube ip


http://192.168.58.2:30349


$ kubectl get httproutes -n apk



Add these to /etc/hosts:

192.168.58.2 am.wso2.com

192.168.58.2 api.am.wso2.com

192.168.58.2 idp.am.wso2.com


These hostnames are used by APIM and APK.


🟩 Step 6 — Access APIM


Disable HSTS for that domain (Chrome advanced)

Not recommended
⚠ Works only if Chrome does NOT preload HSTS for that domain
(am.wso2.com is in preload → so this won’t work)

But for future reference:

chrome://net-internals/#hsts

Delete domain under “Delete domain security policies”.


Publisher:

https://am.wso2.com/publisher


Devportal:

https://am.wso2.com/devportal


Login:

admin / admin




🟥 Step 7 — Create & Deploy an API (EchoAPI)

1. Create API in Publisher

Publisher → Create API → REST API

  • Name: EchoAPI

  • Context: /echo

  • Version: 1.0.0

  • Backend URL: https://httpbin.org/anything

Click Create.

2. Deploy to APK

Go to Deployments → Deploy New Revision
Select Kubernetes Gateway environment.


3. Publish API


  • Save this generated/default key

Go to Lifecycle → Publish.



🟦 Step 8 — Verify Deployment in APK

kubectl get httproutes -n apk



You should see:

["default.gw.wso2.com"]


That means APIM → APK sync is working.

Make sure DNS is fine


Include gateway host entry in /etc/hosts.


192.168.58.2  default.gw.wso2.com




🟨 Step 9 — How to call the API from host (externally)

minikube service apk-wso2-apk-gateway-service -n apk --url


Example:

http://192.168.58.2:32513


🔹 Inside the pod → port 9095 is HTTPS
🔹 NodePort 32513 also expects HTTPS


Check logs for the gateway

kubectl logs deploy/apk-wso2-apk-gateway-runtime-deployment -n apk -c router | tail -n 40


kubectl logs deploy/apk-wso2-apk-gateway-runtime-deployment -n apk -c enforcer | tail -n 40


Call the gateway with HTTPS + hostname

Use the hostname in the URL so SNI matches:

# Internal key from Devportal

TOKEN="<your_internal_key_here>"


curl -v -k "https://default.gw.wso2.com:32513/echo/1.0.0" \

  -H "Internal-Key: $TOKEN"


Notes:

  • https:// because the listener is HTTPS.

  • default.gw.wso2.com in URL so TLS SNI matches the cert/vHost.

  • -k to ignore self-signed cert.

  • Use the Internal-Key header because your JWT has "token_type":"InternalKey".

If instead you generate a Production OAuth token in Devportal, then:

OAUTH_TOKEN="<your_oauth_access_token>"


curl -v -k "https://default.gw.wso2.com:32513/echo/1.0.0" \

  -H "Authorization: Bearer $OAUTH_TOKEN"


You should see a 200 OK and the JSON echoed from https://httpbin.org/anything.




🟩 Step 10 Why this will work (quick recap)


  • Service: apk-wso2-apk-gateway-service
    9095:32513/TCP (HTTPS_9095_listener in Envoy)

  • NodePort: 32513 speaks HTTPS, not HTTP.

  • httproutes show your API is bound to default.gw.wso2.com.

  • Router/enforcer logs show they eventually connect to the control-plane and load config successfully.

So the only missing piece was:

use HTTPS + correct hostname + correct auth header.


$ curl -v -k "https://default.gw.wso2.com:32513/echo/1.0.0" \

  -H "Internal-Key: $TOKEN"

*   Trying 192.168.58.2:32513...

* Connected to default.gw.wso2.com (192.168.58.2) port 32513 (#0)

* ALPN, offering h2

* ALPN, offering http/1.1

* TLSv1.0 (OUT), TLS header, Certificate Status (22):

* TLSv1.3 (OUT), TLS handshake, Client hello (1):

* TLSv1.2 (IN), TLS header, Certificate Status (22):

* TLSv1.3 (IN), TLS handshake, Server hello (2):

* TLSv1.2 (IN), TLS header, Finished (20):

* TLSv1.2 (IN), TLS header, Supplemental data (23):

* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):

* TLSv1.3 (IN), TLS handshake, Certificate (11):

* TLSv1.3 (IN), TLS handshake, CERT verify (15):

* TLSv1.3 (IN), TLS handshake, Finished (20):

* TLSv1.2 (OUT), TLS header, Finished (20):

* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):

* TLSv1.2 (OUT), TLS header, Supplemental data (23):

* TLSv1.3 (OUT), TLS handshake, Finished (20):

* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384

* ALPN, server accepted to use h2

* Server certificate:

*  subject: [NONE]

*  start date: Nov 27 09:34:55 2025 GMT

*  expire date: Feb 25 09:34:55 2026 GMT

*  issuer: CN=apk

*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.

* Using HTTP2, server supports multiplexing

* Connection state changed (HTTP/2 confirmed)

* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0

* TLSv1.2 (OUT), TLS header, Supplemental data (23):

* TLSv1.2 (OUT), TLS header, Supplemental data (23):

* TLSv1.2 (OUT), TLS header, Supplemental data (23):

* Using Stream ID: 1 (easy handle 0x5d7a71ba39f0)

* TLSv1.2 (OUT), TLS header, Supplemental data (23):

> GET /echo/1.0.0 HTTP/2

> Host: default.gw.wso2.com:32513

> user-agent: curl/7.81.0

> accept: */*

> internal-key: eyJraWQiOiJnYXRld2F5X2NlcnRpZmljYXRlX2FsaWFzIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJhZG1pbkBjYXJib24uc3VwZXIiLCJhdWQiOiI0OTczZWU4ZC01ZWZlLTQ5NWItYjhiOC01ZDY3ZjU2Yzk4NjIiLCJpc3MiOiJodHRwOi8vYW0ud3NvMi5jb206NDQzL3Rva2VuIiwia2V5dHlwZSI6IlBST0RVQ1RJT04iLCJzdWJzY3JpYmVkQVBJcyI6W3sibmFtZSI6IkVjaG9BUEkiLCJjb250ZXh0IjoiL2VjaG8vMS4wLjAiLCJ2ZXJzaW9uIjoiMS4wLjAiLCJwdWJsaXNoZXIiOiJhZG1pbiIsInN1YnNjcmlwdGlvblRpZXIiOm51bGwsInN1YnNjcmliZXJUZW5hbnREb21haW4iOm51bGx9XSwiZXhwIjoxNzY0Mjk4MTIwLCJ0b2tlbl90eXBlIjoiSW50ZXJuYWxLZXkiLCJpYXQiOjE3NjQyMzgxMjAsImp0aSI6IjkxOTlmNjk2LTVmMzctNGMyNi1hZDIxLTlkZjE3MmI3MTRmMiJ9.mJ6_qwcu6oX5YMpuB7aUeoAl9P-q3yPr_0YukbWq_hTBd2mxvn8jNfUDbCig19DyX8UQ4O5cUfPV3-Id2_Q23K0rdCqCoNKZGX-AsYU54eI-hPjPWvrz3UIlrsiMmMu_fpWdbOE8WXbKRhRE44JRVY5W8NWLAlunZf5Z9bWni0qZbdBhqszJ55OWDRgtmIoxE1TVW_9wt-eYsY-g5yk8uyTdV0oICFbkcMKPN_MPkMsibciP88nRrXaniXGZDE8xVtMSbaKt9NYaRgttveoA4Wru8UKTAku3e6rOJNavRTsMh1lUIn7h39A53UtkmoEn-LOraHqjavA9ObkBatiYpQ

* TLSv1.2 (IN), TLS header, Supplemental data (23):

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* old SSL session ID is stale, removing

* TLSv1.2 (IN), TLS header, Supplemental data (23):

* Connection state changed (MAX_CONCURRENT_STREAMS == 2147483647)!

* TLSv1.2 (OUT), TLS header, Supplemental data (23):

* TLSv1.2 (IN), TLS header, Supplemental data (23):

< HTTP/2 200 

< date: Thu, 27 Nov 2025 10:45:02 GMT

< content-type: application/json

< content-length: 341

< server: envoy

< access-control-allow-origin: *

< access-control-allow-credentials: true

< vary: Accept-Encoding

{

  "args": {}, 

  "data": "", 

  "files": {}, 

  "form": {}, 

  "headers": {

    "Accept": "*/*", 

    "Host": "httpbin.org", 

    "User-Agent": "curl/7.81.0", 

    "X-Amzn-Trace-Id": "Root=1-69282bad-019b76fc70de68d24dc4234b"

  }, 

  "json": null, 

  "method": "GET", 

  "origin": "92.244.7.43", 

  "url": "https://httpbin.org/anything"

}

* Connection #0 to host default.gw.wso2.com left intact



🎉 Conclusion

You now have a fully working:

  • WSO2 API Manager control plane

  • WSO2 APK gateway (Envoy)

  • API deployment + publishing

  • HTTPS API invocation via NodePort

  • End-to-end Envoy routing

This is the recommended WSO2 architecture for Kubernetes.