Deploy Kubernetes
This page describes the recommended Kubernetes deployment model for the Rust
light-gateway image from light-fabric/apps/light-gateway.
Use this model when light-gateway runs as a microgateway in front of backend
MCP servers. The pod starts from local bootstrap config, downloads runtime
config from config-server into config-cache, starts Pingora, and registers the
gateway with controller.
Recommended Model
Deploy the gateway as a normal single-container Kubernetes workload:
Deploymentfor the gateway pod.Servicefor stable in-cluster access.ConfigMapfor bootstrap config and non-secret values.Secretfor bearer tokens and config passwords.emptyDirorPersistentVolumeClaimforconfig-cache.- Optional
Ingress,Gateway API,NodePort, orLoadBalancerfor external client access.
Keep gateway behavior such as MCP route definitions, access-control rules, backend MCP targets, and runtime TLS files in config-server. The Kubernetes bootstrap config should only contain enough information for startup, trust, and registration.
Image
Build the image from the workspace root:
./apps/light-gateway/build.sh 2.2.1
For local testing without pushing:
./apps/light-gateway/build.sh 2.2.1 --local
Use immutable tags in Kubernetes. Avoid latest for customer deployments.
The runtime image uses:
/app/light-gateway
/app/config -> /config
/app/config-cache
The process runs as the image user gateway. Mount /config for bootstrap
config and make /app/config-cache writable.
Runtime Paths
Recommended container layout:
/config/
startup.yml
server.yml
portal-registry.yml
client.yml
values.yml
ca.pem
/app/config-cache/
values.yml
downloaded certs and files
Use a read-only ConfigMap for /config. Use a writable volume for
/app/config-cache.
For most deployments, use emptyDir for config-cache. This gives each pod a
fresh cache and avoids accidentally keeping stale config across pod replacement.
Use a PersistentVolumeClaim only when the customer explicitly wants the
gateway to restart from the last downloaded config during a config-server
outage. A persistent cache improves outage tolerance but can also preserve stale
runtime state.
Registration Address
In Kubernetes, do not register the pod IP. Pod IPs are ephemeral.
If controller and callers are inside the same cluster, advertise the Service DNS name:
server.advertisedAddress: ai-microgateway.light-gateway
The pattern is:
<service-name>.<namespace>
The port is still registered separately from the host/address.
If controller or callers are outside the cluster, advertise the externally reachable DNS name instead, such as the Ingress or LoadBalancer hostname:
server.advertisedAddress: mcp-gateway.customer.example.com
For the Rust gateway, this is configured with server.advertisedAddress. The
Java gateway template uses STATUS_HOST_IP; that is a light-4j-specific hook
and is not the Rust gateway contract.
Bootstrap Config
Example values.yml for an in-cluster controller and config-server:
startup.host: customer.example.com
startup.timeout: 3000
startup.connectTimeout: 3000
startup.bootstrapCaCertPath: config/ca.pem
light-config-server-uri: https://config-server.lightapi.svc.cluster.local:8435
server.serviceId: com.customer.mcp-gateway-1.0.0
server.environment: prod
server.ip: 0.0.0.0
server.advertisedAddress: ai-microgateway.light-gateway
server.httpPort: 8080
server.enableHttp: true
server.httpsPort: 8443
server.enableHttps: false
server.enableRegistry: true
server.startOnRegistryFailure: true
portalRegistry.portalUrl: https://controller.lightapi.svc.cluster.local:8438
client.caCertPath: config/ca.pem
client.verifyHostname: true
Example startup.yml:
host: ${startup.host:dev.lightapi.net}
serviceId: ${server.serviceId:com.networknt.light-gateway-1.0.0}
envTag: ${server.environment:dev}
acceptHeader: application/yaml
timeout: ${startup.timeout:3000}
connectTimeout: ${startup.connectTimeout:3000}
configServerUri: ${light-config-server-uri:https://local.localhost}
authorization: ${light_portal_authorization:}
bootstrapCaCertPath: ${startup.bootstrapCaCertPath:config/ca.pem}
Example server.yml:
ip: ${server.ip:0.0.0.0}
advertisedAddress: ${server.advertisedAddress:127.0.0.1}
httpPort: ${server.httpPort:8080}
enableHttp: ${server.enableHttp:true}
httpsPort: ${server.httpsPort:8443}
enableHttps: ${server.enableHttps:false}
tlsCertPath: ${server.tlsCertPath:}
tlsKeyPath: ${server.tlsKeyPath:}
serviceId: ${server.serviceId:com.networknt.light-gateway-1.0.0}
enableRegistry: ${server.enableRegistry:true}
startOnRegistryFailure: ${server.startOnRegistryFailure:true}
dynamicPort: ${server.dynamicPort:false}
environment: ${server.environment:dev}
shutdownGracefulPeriod: ${server.shutdownGracefulPeriod:2000}
Example portal-registry.yml:
portalUrl: ${portalRegistry.portalUrl:https://localhost:8438}
portalToken: ${light_portal_authorization:}
controllerDiscoveryToken: ${portalRegistry.controllerDiscoveryToken:}
Example client.yml:
tls:
caCertPath: ${client.caCertPath:config/ca.pem}
verifyHostname: ${client.verifyHostname:true}
Use the customer CA in ca.pem. Do not disable hostname verification in
production to work around certificate SAN problems.
Secrets
Store the portal bearer token and optional config password in a Kubernetes
Secret.
Example:
apiVersion: v1
kind: Secret
metadata:
name: light-gateway-secret
namespace: light-gateway
type: Opaque
stringData:
LIGHT_PORTAL_AUTHORIZATION: "Bearer <token>"
light_4j_config_password: "<config-password-if-needed>"
LIGHT_PORTAL_AUTHORIZATION is used for config-server bootstrap. It is also
used by portal registry startup when portal-registry.yml resolves
portalToken from light_portal_authorization.
Do not store real bearer tokens in Git, ConfigMaps, Helm values committed to the repo, or rendered deployment examples.
Example Manifests
Create the namespace separately:
kubectl create namespace light-gateway
If deploying through light-deployer, keep Namespace out of the rendered
bundle because deployer policy may block cluster-scoped resources.
Example bootstrap ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: light-gateway-bootstrap
namespace: light-gateway
data:
values.yml: |
startup.host: customer.example.com
startup.timeout: 3000
startup.connectTimeout: 3000
startup.bootstrapCaCertPath: config/ca.pem
light-config-server-uri: https://config-server.lightapi.svc.cluster.local:8435
server.serviceId: com.customer.mcp-gateway-1.0.0
server.environment: prod
server.ip: 0.0.0.0
server.advertisedAddress: ai-microgateway.light-gateway
server.httpPort: 8080
server.enableHttp: true
server.httpsPort: 8443
server.enableHttps: false
server.enableRegistry: true
server.startOnRegistryFailure: true
portalRegistry.portalUrl: https://controller.lightapi.svc.cluster.local:8438
client.caCertPath: config/ca.pem
client.verifyHostname: true
startup.yml: |
host: ${startup.host:dev.lightapi.net}
serviceId: ${server.serviceId:com.networknt.light-gateway-1.0.0}
envTag: ${server.environment:dev}
acceptHeader: application/yaml
timeout: ${startup.timeout:3000}
connectTimeout: ${startup.connectTimeout:3000}
configServerUri: ${light-config-server-uri:https://local.localhost}
authorization: ${light_portal_authorization:}
bootstrapCaCertPath: ${startup.bootstrapCaCertPath:config/ca.pem}
server.yml: |
ip: ${server.ip:0.0.0.0}
advertisedAddress: ${server.advertisedAddress:127.0.0.1}
httpPort: ${server.httpPort:8080}
enableHttp: ${server.enableHttp:true}
httpsPort: ${server.httpsPort:8443}
enableHttps: ${server.enableHttps:false}
tlsCertPath: ${server.tlsCertPath:}
tlsKeyPath: ${server.tlsKeyPath:}
serviceId: ${server.serviceId:com.networknt.light-gateway-1.0.0}
enableRegistry: ${server.enableRegistry:true}
startOnRegistryFailure: ${server.startOnRegistryFailure:true}
dynamicPort: ${server.dynamicPort:false}
environment: ${server.environment:dev}
shutdownGracefulPeriod: ${server.shutdownGracefulPeriod:2000}
portal-registry.yml: |
portalUrl: ${portalRegistry.portalUrl:https://localhost:8438}
portalToken: ${light_portal_authorization:}
controllerDiscoveryToken: ${portalRegistry.controllerDiscoveryToken:}
client.yml: |
tls:
caCertPath: ${client.caCertPath:config/ca.pem}
verifyHostname: ${client.verifyHostname:true}
ca.pem: |
-----BEGIN CERTIFICATE-----
<customer-ca-certificate>
-----END CERTIFICATE-----
Example Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-microgateway
namespace: light-gateway
labels:
app: ai-microgateway
spec:
replicas: 2
selector:
matchLabels:
app: ai-microgateway
template:
metadata:
labels:
app: ai-microgateway
spec:
securityContext:
fsGroup: 999
fsGroupChangePolicy: OnRootMismatch
containers:
- name: light-gateway
image: networknt/light-gateway:2.2.1
imagePullPolicy: IfNotPresent
env:
- name: LIGHT_PORTAL_AUTHORIZATION
valueFrom:
secretKeyRef:
name: light-gateway-secret
key: LIGHT_PORTAL_AUTHORIZATION
- name: light_4j_config_password
valueFrom:
secretKeyRef:
name: light-gateway-secret
key: light_4j_config_password
optional: true
- name: RUST_LOG
value: info
ports:
- name: http
containerPort: 8080
- name: https
containerPort: 8443
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 30
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: "1"
memory: 512Mi
volumeMounts:
- name: bootstrap-config
mountPath: /config
readOnly: true
- name: config-cache
mountPath: /app/config-cache
volumes:
- name: bootstrap-config
configMap:
name: light-gateway-bootstrap
- name: config-cache
emptyDir: {}
The example uses fsGroup: 999, which matches the default gateway group in the
current image. Adjust it if the image user or group changes.
If HTTP is disabled and only HTTPS is enabled, change the probes to an HTTPS probe or a TCP probe.
Example Service:
apiVersion: v1
kind: Service
metadata:
name: ai-microgateway
namespace: light-gateway
spec:
type: ClusterIP
selector:
app: ai-microgateway
ports:
- name: http
port: 8080
targetPort: http
- name: https
port: 8443
targetPort: https
For external access, add an Ingress, Gateway API route, NodePort, or
LoadBalancer according to the customer cluster standard. If external clients
or controller use that external path, set server.advertisedAddress to the same
externally reachable DNS name.
Apply With Kubectl
Apply manifests in this order:
kubectl apply -f namespace.yml
kubectl apply -f secret.yml
kubectl apply -f configmap.yml
kubectl apply -f deployment.yml
kubectl apply -f service.yml
Check rollout:
kubectl -n light-gateway rollout status deploy/ai-microgateway
kubectl -n light-gateway get pods -l app=ai-microgateway
kubectl -n light-gateway logs deploy/ai-microgateway
For local testing with a ClusterIP Service:
kubectl -n light-gateway port-forward svc/ai-microgateway 8080:8080 8443:8443
Deploy Through Light-Deployer
When light-deployer runs outside the cluster and has
LIGHT_DEPLOYER_TEMPLATE_BASE_DIR set, repoUrl: "local" can point to local
templates.
When light-deployer runs inside Kubernetes, use a real Git URL:
{
"template": {
"repoUrl": "https://github.com/networknt/light-fabric.git",
"ref": "main",
"path": "apps/light-gateway/k8s/light-gateway"
}
}
Do not use repoUrl: "local" for an in-cluster deployer unless the template
repo is mounted into the deployer container and
LIGHT_DEPLOYER_TEMPLATE_BASE_DIR points to it.
The in-cluster deployer checks out repoUrl at ref and reads manifests from
template.path.
Keep Namespace out of templates rendered by light-deployer if the deployer
policy blocks cluster-scoped resources. Create the namespace separately:
kubectl create namespace light-gateway
Config-Server Requirements
Before deploying the gateway pod, config-server should already have config for the tuple used by startup:
host = startup.host
serviceId = server.serviceId
envTag = server.environment
At minimum, config-server should return runtime config for:
handler.ymlmcp-router.ymlaccess-control.ymlandrule.ymlwhen MCP authorization is enabled.security.yml,unified-security.yml, or other active auth config.websocket-router.ymlwhen WebSocket MCP/BFF routing is enabled.- Any downstream client, token, or registry config required by the selected handlers.
The pod bootstrap files should stay small and stable. Normal route, policy, and backend changes should go through config-server and controller reload flows.
Startup Flow
Expected runtime flow:
Kubernetes starts pod
-> /app/light-gateway
-> read /app/config -> /config bootstrap files
-> call config-server with LIGHT_PORTAL_AUTHORIZATION
-> write downloaded config and files into /app/config-cache
-> start Pingora with resolved runtime config
-> register gateway to controller using portalRegistry.portalUrl
-> advertise server.advertisedAddress and configured port
-> route protected MCP traffic to backend MCP servers
If config-server is unavailable and /app/config-cache/values.yml exists, the
runtime can continue from cached config. With emptyDir, that cache disappears
when the pod is recreated. With a PVC, it can survive pod replacement.
Upgrade And Rollback
Use Kubernetes rolling updates with immutable image tags:
kubectl -n light-gateway set image deploy/ai-microgateway \
light-gateway=networknt/light-gateway:2.2.2
kubectl -n light-gateway rollout status deploy/ai-microgateway
Rollback:
kubectl -n light-gateway rollout undo deploy/ai-microgateway
For production, prefer changing only one variable at a time: either image tag or config-server runtime config, not both in the same rollout.
Validation Checklist
After deployment:
kubectl -n light-gateway rollout status deploy/ai-microgatewaysucceeds.- Pods are ready and restart count is stable.
- Logs show successful config-server bootstrap.
- Logs show successful controller registration.
- Controller shows the gateway registered with the expected service id, environment, host, and port.
server.advertisedAddressis reachable from the controller.- The Service responds on
/health. - MCP
tools/listreaches the gateway. - MCP
tools/callreaches the backend MCP server. - A pod restart still starts cleanly with the selected cache policy.
Security Checklist
- Keep bearer tokens in Kubernetes
Secret, notConfigMap. - Use customer CA trust and keep
client.verifyHostname: truein production. - Use immutable image tags and image pull credentials from Kubernetes secrets when the registry is private.
- Run as the non-root image user.
- Make
/configread-only. - Make only
/app/config-cachewritable. - Restrict ingress traffic to required gateway ports.
- Restrict egress traffic to config-server, controller, token/key services, and backend MCP servers.
- Rotate
LIGHT_PORTAL_AUTHORIZATIONthrough the customer secret process.