Gen Web¶
The axion-gen-web Helm chart deploys the Next.js 15 frontend that combines consumer dashboards and the analyst chat panel. Cross-source SQL execution lives in Gen API, not in the browser.
Workloads deployed¶
| Component | Role | See |
|---|---|---|
web |
Next.js server + static asset serving. The same process hosts the BFF that proxies LLM calls and the ask_data tool to Gen Backend. |
Gen overview |
Gen Web has no database of its own — chat history lives in browser localStorage.
Prerequisites¶
- Kubernetes cluster (v1.27+)
- Helm 3.12+
kubectlconfigured for your cluster- Axion Gen Backend API deployed and reachable from the cluster (see Gen Backend)
- LLM provider — OpenAI-compatible, Anthropic, on-prem, or
mock - URBI / 2GIS MapGL — required for map widgets (partner-supplied API endpoint and key)
- OIDC IdP — same one used by Sense and Gen Backend
- (Optional) Mapbox token, Langfuse, MOMAH GPT
1. Prepare your values file¶
Create custom-values.yaml and fill in all required values below. Helm will refuse to render the chart if any of them are missing or empty.
Required values¶
| Value path | Description |
|---|---|
web.image.repository |
Full image path in your private registry |
web.image.tag |
Image version tag |
The chart additionally reads three runtime env vars under web.env that are forwarded to the browser via ConfigProvider:
OIDC_REDIRECT_URI— post-login OIDC callback URLURBI_API,URBI_KEY— 2GIS MapGL endpoint + key
BACKEND_URL defaults to http://localhost:5501 and almost always needs to be overridden to the in-cluster Gen Backend service URL.
Conditionally required¶
| Value path | When required |
|---|---|
web.ingress.hosts |
If web.ingress.enabled: true |
web.certificate.issuerRef.name |
If web.certificate.enabled: true |
web.certificate.dnsNames |
If web.certificate.enabled: true |
Optional (sensible defaults provided)¶
| Value path | Default | Notes |
|---|---|---|
web.replicaCount |
1 |
Increase for HA |
web.service.type |
ClusterIP |
Change to LoadBalancer/NodePort if needed |
web.service.port |
80 |
Service port exposed to the cluster |
web.service.targetPort |
3000 |
Container port (Next.js default) |
web.env |
{} |
Runtime environment variables (see below) |
web.secrets |
{} |
Sensitive values stored in a Kubernetes Secret |
web.resources |
256Mi/512Mi mem | Tune per workload |
web.hostAliases |
[] |
Extra /etc/hosts entries |
global.imagePullSecrets |
ar-registry-secret |
Set if your registry is private |
Runtime environment variables¶
These can be changed without rebuilding the image. Set non-sensitive values in web.env and sensitive values in web.secrets — secrets are stored in a Kubernetes Secret, injected via envFrom, and the pod automatically restarts on change.
web.env — plain environment variables¶
| Variable | Default | Description |
|---|---|---|
OIDC_REDIRECT_URI |
— (required) | Post-login OIDC redirect URL (passed to client at runtime) |
URBI_API |
— (required) | URBI/2GIS MapGL API endpoint (passed to client at runtime) |
URBI_KEY |
— (required) | URBI/2GIS MapGL API key (passed to client at runtime) |
MAPBOX_TOKEN |
— | Mapbox token for map widgets (passed to client at runtime, optional) |
BACKEND_URL |
http://localhost:5501 |
Axion Gen Backend API URL |
LLM_PROVIDER |
openai_compatible |
LLM provider type: openai_compatible, anthropic, on_prem, or mock |
LLM_BASE_URL |
— | LLM provider base URL (e.g. https://api.openai.com/v1) |
LLM_MODEL |
gpt-4o-mini |
Model name to use |
AGENT_MODE |
ask_only |
ask_only (Q&A only) or full (all tools including DB / charts / dashboards) |
AGENT_MAX_CONTEXT_MESSAGES |
40 |
Max messages kept in LLM context window |
MOMAH_GPT_API_URL |
— | MOMAH GPT API endpoint for external data-question agent |
MOMAH_GPT_AUTH_STRATEGY |
— | Auth strategy: static (use token as-is) or forward (forward user's SSO token) |
LANGFUSE_BASE_URL |
https://cloud.langfuse.com |
Langfuse instance URL (tracing disabled if keys absent) |
OIDC_REDIRECT_URI, URBI_API, URBI_KEY, and MAPBOX_TOKEN are read at runtime by the Next.js server and passed to the browser via ConfigProvider. Changing them does not require rebuilding the Docker image — restart the container with the new values.
web.secrets — sensitive values (stored in Kubernetes Secret)¶
| Variable | Description |
|---|---|
LLM_API_KEY |
LLM provider API key |
MOMAH_GPT_API_TOKEN |
MOMAH GPT API authentication token |
LANGFUSE_SECRET_KEY |
Langfuse observability secret key |
LANGFUSE_PUBLIC_KEY |
Langfuse observability public key |
Example configuration¶
web:
env:
OIDC_REDIRECT_URI: "https://app.partner.com/api/auth/callback/oidc"
URBI_API: "https://mapgl.urbi.ae/api"
URBI_KEY: "your-urbi-key"
BACKEND_URL: "http://axion-gen-api:80"
LLM_PROVIDER: "openai_compatible"
LLM_BASE_URL: "https://api.openai.com/v1"
LLM_MODEL: "gpt-4o-mini"
AGENT_MODE: "ask_only"
LANGFUSE_BASE_URL: "https://us.cloud.langfuse.com"
secrets:
LLM_API_KEY: "change-me"
LANGFUSE_SECRET_KEY: "change-me"
LANGFUSE_PUBLIC_KEY: "change-me"
Minimal custom-values.yaml¶
A working file with only the required values (everything else uses chart defaults):
web:
image:
repository: "registry.partner.com/axion/gen-web"
tag: "1.0.0"
env:
OIDC_REDIRECT_URI: "https://app.partner.com/api/auth/callback/oidc"
URBI_API: "https://mapgl.urbi.ae/api"
URBI_KEY: "your-urbi-key"
BACKEND_URL: "http://axion-gen-api:80"
2. Get Helm chart and container image¶
2.1 Obtain a service-account key¶
Request sa-key.json from @v.loboda. This key grants read access to the Axion artifact registry.
2.2 Pull the container image¶
cat sa-key.json | docker login -u _json_key --password-stdin "https://europe-west1-docker.pkg.dev"
docker pull europe-west1-docker.pkg.dev/axionx-infra/axion/gen-web:latest
2.3 Pull the Helm chart¶
cat sa-key.json | helm registry login europe-west1-docker.pkg.dev -u _json_key --password-stdin
helm pull oci://europe-west1-docker.pkg.dev/axionx-infra/axion/helm/axion-gen-web --version 0.0.0-latest
2.4 Push the image to your private registry¶
docker tag europe-west1-docker.pkg.dev/axionx-infra/axion/gen-web:latest registry.example.com/axion/gen-web:latest
docker push registry.example.com/axion/gen-web:latest
If your registry requires authentication:
kubectl create namespace axion-gen-web
kubectl create secret docker-registry registry-creds \
-n axion-gen-web \
--docker-server=registry.example.com \
--docker-username=<user> \
--docker-password=<password>
2.5 Rebuild the image (optional)¶
docker build -f frontend/Dockerfile frontend/ \
-t registry.example.com/axion/gen-web:1.0.0
docker push registry.example.com/axion/gen-web:1.0.0
3. Install¶
helm upgrade --install axion-gen-web \
oci://europe-west1-docker.pkg.dev/axionx-infra/axion/helm/axion-gen-web \
--version 0.0.0-latest \
-n axion-gen --create-namespace \
-f custom-values.yaml \
--atomic --wait --timeout 10m
The chart deploys into the axion-gen namespace by convention so it sits next to the backend it talks to. The --atomic flag rolls back automatically on failure.
4. Verify¶
kubectl get pods -n axion-gen
helm -n axion-gen status axion-gen-web
kubectl port-forward svc/axion-gen-web 8080:80 -n axion-gen
curl http://localhost:8080/api/health
5. Expose the service (optional)¶
Option A — Ingress¶
web:
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
hosts:
- host: app.partner.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- app.partner.example.com
secretName: axion-gen-web-tls
Option B — NodePort / LoadBalancer¶
6. Upgrade and rollback¶
Update web.image.tag and re-run the install command. No migrations.
helm -n axion-gen history axion-gen-web
helm -n axion-gen rollback axion-gen-web <REVISION> --wait --timeout 10m
7. Uninstall¶
Troubleshooting¶
# render manifests locally to validate values
helm template axion-gen-web oci://europe-west1-docker.pkg.dev/axionx-infra/axion/helm/axion-gen-web \
--version 0.0.0-latest -f custom-values.yaml
# application logs
kubectl logs -l app.kubernetes.io/component=web -n axion-gen --tail=100
# describe pod for events/errors
kubectl describe pod -l app.kubernetes.io/component=web -n axion-gen
# Kubernetes events
kubectl -n axion-gen get events --sort-by=.lastTimestamp