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 APIM control plane
Deploy APK gateway (Envoy-based)
Publish APIs from APIM
Deploy them to APK
Call them securely using HTTPS
Troubleshoot real-world issues (TLS, NodePorts, hostnames, routing)
This post is based on a real, complete setup — not a theoretical guide.
📘 Architecture
Your final working architecture looks like this:
Client → APK Gateway (Envoy) → Backend (httpbin)
↑
APIM Control Plane (Publisher/Devportal)
🧰 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:
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.
No comments:
Post a Comment