Deploying to Kubernetes
kweblens ships as one Spring Boot jar that runs three ways — standalone, as a container, or as an in-cluster pod — and the same binary can monitor the hosting cluster, external clusters, or both. Only the configuration changes.
This page covers the in-cluster deployment: the Helm chart in deploy/helm/kweblens, the
image, how the pod finds its credentials, and the RBAC it needs.
|
There is no image on GHCR, and the workflow that would push one has never run.
The chart’s |
1. The image
Built by Cloud Native Buildpacks via Spring Boot’s build-image goal.
There is no Dockerfile. The docker Maven profile carries the configuration, and it also
re-adds kweblens-ui and kweblens-web to the reactor (they live in an activeByDefault
profile that any explicit -P deactivates):
# local image, tagged kweblens:<project.version>
./mvnw -Pdocker -pl kweblens-web -am package
# named tag, pushed straight to a registry
./mvnw -Pdocker -pl kweblens-web -am package \
-Ddocker.image.name=registry.example.com/kweblens:<version> \
-Ddocker.publish=true
Only kweblens-web declares the Spring Boot plugin, so only it produces an image. A Docker
daemon must be running. scripts/deploy-k8s.sh wraps build, push and helm upgrade in one
arg-driven command:
scripts/deploy-k8s.sh \
--registry registry.example.com \
--namespace kweblens \
--pull-secret regcred \
--values ./my-values.yaml
The script has no defaults of its own beyond the namespace and release name (kweblens);
--registry is required and the tag defaults to the Maven project version.
2. Installing the chart
helm upgrade --install kweblens deploy/helm/kweblens \
--namespace kweblens --create-namespace \
--set image.repository=registry.example.com/kweblens \
--set image.tag=<version> \
--set auth.openMode=false \
--set auth.existingSecret=kweblens-auth
The chart renders a Deployment, a Service, an optional Ingress, an optional ServiceAccount, an
optional PVC, and — for hosting-cluster monitoring — a ClusterRole and binding. The chart’s
appVersion tracks the project version; the image tag defaults to appVersion when
image.tag is empty.
3. Which clusters the pod sees
clusterScope decides, and the two halves combine.
| Setting | Effect |
|---|---|
|
Sets |
|
A list of |
|
Name of a Secret with a single key |
In-cluster vs out-of-cluster is a detection, not a setting. Building a fabric8 client only resolves configuration — the first API call is what reaches the cluster. So a bad kubeconfig fails lazily on first use, not at boot, and the pod starts either way.
One cluster is registered per kubeconfig context, with the context name as its id. Do not
assume an id called default exists — resolve ids from GET /api/v1/clusters. See
Connecting clusters for the runtime add/edit/remove API and where those
credentials are stored.
4. RBAC
Rendered only when rbac.create and clusterScope.local are both true — an instance that
monitors only external clusters carries its credentials in the mounted kubeconfig instead.
rbac.role picks one of two ClusterRoles, both bound to the pod’s ServiceAccount:
| Role | Grants |
|---|---|
|
|
|
The above plus |
Both roles additionally grant get on services/proxy, nodes/proxy and pods/log (metrics
via the API-server service proxy, and log streaming), and get on the non-resource URLs
/metrics, /healthz, /version.
editor deliberately omits escalate, bind and impersonate, so the grant cannot be used
to widen itself.
|
|
A second, namespaced Role is rendered when rbac.clusterStore is true (the default):
get/list/create/update/patch/delete on secrets in kweblens’s own namespace. That
is not about the cluster kweblens browses — it is how runtime-added clusters
(POST /api/v1/clusters) are persisted, one Secret per cluster. It is independent of
clusterScope.local; an instance that watches only external clusters is exactly the one that
needs it. Set it false to refuse the grant, and either point kweblens.cluster-store.path
at a mounted volume with mode=file, or accept that clusters can only come from
configuration.
5. The admin login
The chart defaults to auth.openMode: true, matching the application default. Change it.
auth.existingSecret names a Secret loaded wholesale into the container’s environment
(envFrom), so it supplies the credential without it ever appearing in a values file:
kubectl -n kweblens create secret generic kweblens-auth \
--from-literal=KWEBLENS_SECURITY_ADMIN_USERNAME=admin \
--from-literal=KWEBLENS_SECURITY_ADMIN_PASSWORD='<strong-password>'
helm upgrade --install kweblens deploy/helm/kweblens \
--set auth.existingSecret=kweblens-auth --set auth.openMode=false
The same Secret can carry ANTHROPIC_API_KEY if you enable the AI enrichment. If no password
is supplied, kweblens generates one per run and logs it at WARN — recoverable with
kubectl logs, but it changes on every restart.
6. Networking
service.type defaults to ClusterIP on port 8080; the same value is passed to the
container as PORT and used as the container port, so changing it changes both.
The Ingress is off by default. When ingress.enabled=true, ingress.host is required —
the template fails rendering rather than inventing a hostname. ingress.className defaults to
traefik and ingress.tls to true (a TLS block listing the host, with no secretName, so
it expects a default certificate or an issuer annotation). Put TLS termination and your
identity provider here.
Liveness and readiness probes hit /actuator/health/liveness and /actuator/health/readiness,
which are public in both security modes.
7. Storage
persistence is off by default. Enabling it provisions a ReadWriteOnce PVC and sets
KWEBLENS_HELM_HOME to persistence.mountPath (default /data/helm), so the Helm repository
list, index cache and saved values library survive a restart. Because the volume is RWO the
chart forces replicas: 1 and a Recreate strategy while it is enabled.
8. Resources — and why the default matters
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
memory: 1Gi
There is no CPU limit, and no JAVA_TOOL_OPTIONS. The JVM therefore defaults to about a
quarter of the memory limit — roughly 256 MB of heap — and a container that exceeds its limit
is OOM-killed, not slowed down. That number is load-bearing at scale: see
Performance and scale, which measures exactly how many objects fit
under it and what bounds the heap.
The pod runs non-root as uid 1002 / gid 1001 (matching the buildpack image), with
allowPrivilegeEscalation: false and all capabilities dropped. readOnlyRootFilesystem is
false — the JVM and buildpack layers write to the container filesystem.
9. Chart values reference
| Value | Purpose |
|---|---|
|
Image. |
|
e.g. |
|
Which clusters this instance monitors. |
|
ClusterRole for the hosting cluster ( |
|
Defaults to creating one named after the release fullname. |
|
Admin credential Secret, and the |
|
PVC for |
|
Also sets |
|
|
|
Requests and limits. See the warning above about the default heap. |
|
|
|
A list of |
|
Non-root defaults matching the buildpack image. |
|
The usual scheduling knobs. |
Chart defaults are deliberately neutral placeholders: no registry, no ingress host, no pull secret. Environment-specific values belong in your own values file, never in the chart.
10. The other two run modes
Standalone jar — every context in the ambient kubeconfig becomes a cluster:
KUBECONFIG=~/.kube/config \
KWEBLENS_SECURITY_ADMIN_USERNAME=admin \
KWEBLENS_SECURITY_ADMIN_PASSWORD=change-me \
java -jar kweblens-web/target/kweblens.jar
Container, outside a cluster — deploy/docker/compose.yaml mounts a host kubeconfig
read-only at /kubeconfig and sets KWEBLENS_LOAD_KUBECONFIG=true:
KWEBLENS_KUBECONFIG=/path/to/kubeconfig \
KWEBLENS_ADMIN_PASSWORD='<strong-password>' \
docker compose -f deploy/docker/compose.yaml up
The compose file defaults the admin password to admin for a throwaway local run. Override
it for anything else.
|
A kubeconfig is a credential. |
11. Related
-
Security and identity — read before exposing this
-
Performance and scale — what the 1 GiB limit actually buys
-
Connecting clusters — adding clusters at runtime, and credential storage
-
Configuration — every
kweblens.*property