Deploy
Kubernetes and Helm
Deploying Secronyx with the bundled Helm chart — the values that map to binary flags, the render-time guards on authentication and TLS, scopes and enableSensitive, host mounts, and the Deployment/DaemonSet and NetworkPolicy decisions the chart leaves to you.
The chart at charts/secronyx (chart version 0.1.0, appVersion: "1.0.0") installs Secronyx as an HTTP-transport workload. It is a thin, opinionated wrapper: almost every value maps to a flag on the binary, and the chart refuses to render configurations that the binary would refuse to start. This page describes the templates as they are shipped.
kubectl create secret generic secronyx-token \
--from-literal=token="$(openssl rand -base64 32)"
kubectl create secret tls secronyx-tls --cert=cert.pem --key=key.pem
helm install secronyx ./charts/secronyx \
--set mcp.auth.token.secretRef.name=secronyx-token \
--set mcp.tls.enabled=true \
--set mcp.tls.secretName=secronyx-tlsRender-time guards
templates/deployment.yaml begins with five checks that call fail. They exist so you get the reason at helm install time instead of a CrashLoopBackOff with the same message in the pod log.
| Condition | Message (abbreviated) |
|---|---|
auth.enabled=false and allowInsecure=false |
"…exposes an unauthenticated read-only view of every node the pod runs on." |
auth.enabled=true with no method set |
"…Set one of mcp.auth.token.secretRef.name, mcp.auth.oidc.enabled or mcp.auth.oauth.enabled." |
| More than one method set | "configure exactly one of mcp.auth.token, mcp.auth.oidc and mcp.auth.oauth." |
auth.enabled=true, tls.enabled=false, allowInsecure=false |
"authentication without TLS sends credentials in plaintext across the pod network." |
tls.enabled=true with empty tls.secretName |
"requires mcp.tls.secretName (a kubernetes.io/tls Secret)." |
The reasoning is the same as on the host: a pod must bind 0.0.0.0:<service.port> to be reachable through its Service, and 0.0.0.0 is not loopback, so the binary's exposure rules apply in full. mcp.allowInsecure=true passes --allow-unauthenticated and is the escape hatch for a cluster where a mesh sidecar or a same-node ingress terminates TLS — or for a throwaway development cluster. NOTES.txt prints a warning after any install that sets it.
Values that become flags
The container always runs --transport http --listen 0.0.0.0:<service.port>. The rest is assembled from values:
| Value | Flag |
|---|---|
mcp.allowInsecure: true |
--allow-unauthenticated |
mcp.tls.enabled: true |
--tls-cert /etc/mcp/tls/tls.crt --tls-key /etc/mcp/tls/tls.key |
mcp.redact: false |
--no-redact |
mcp.scopes.<name>: true |
joined into one --scopes a,b,c |
mcp.scopes.sensitive: true |
additionally --enable-sensitive |
mcp.audit.enabled: true |
--audit-output <mcp.audit.output> |
mcp.audit.enabled: false |
--no-audit |
mcp.auth.oidc.enabled |
--oidc-issuer, --oidc-audience |
mcp.auth.oauth.enabled |
--auth-server, --client-id, --client-secret "$(MCP_CLIENT_SECRET)" |
The bearer token is never put on the command line. When mcp.auth.token.secretRef.name is set, the chart injects SECRONYX_TOKEN from that Secret and the binary picks it up from the environment. The OAuth client secret is injected the same way as MCP_CLIENT_SECRET and referenced with Kubernetes' $(VAR) argument expansion.
Scopes
mcp.scopes is a map of every scope the tool catalogue uses, and each entry is a switch on the tool inventory itself, not a runtime filter. A scope set to false is never registered, so its tools do not appear in tools/list and cannot be called on any transport by any identity. The chart defaults:
mcp:
scopes:
core: true
logs: true
hooks: true
hardware: true
resources: true
state: true
software: true
triage: true
security: true
storage: true
network: true
analytics: true
alerts: true
compliance: true
consumer: true
enhanced: true
report: true
windows: false
sensitive: falsewindows is off because those collectors are Windows-only and return empty results on any other platform; sensitive is off because it covers credentials, user accounts, sudo and SSH configuration, certificates and process environments. Turning it on requires nothing but --set mcp.scopes.sensitive=true, which is precisely why it should be a reviewed change. A useful production narrowing is to invert the default — keep core, logs, triage and state, drop the rest — which also cuts the tools/list payload an AI client has to reason over. See Scopes and authorization.
The pod
podSecurityContext sets runAsNonRoot: true with uid/gid/fsGroup 1000. The container securityContext sets allowPrivilegeEscalation: false, capabilities.drop: [ALL], readOnlyRootFilesystem: true and runAsNonRoot: true. Because the root filesystem is read-only, leave mcp.audit.output at /dev/stdout (the default) unless you add a writable volume through extraVolumes/extraVolumeMounts; a file path that cannot be opened makes the audit provider fall back to stderr rather than go dark.
Liveness and readiness both probe /health on the http port with initialDelaySeconds: 5 and periodSeconds: 10, and the scheme flips to HTTPS when mcp.tls.enabled is true. /health is registered outside requireAuth, so probes need no credentials. It is not the only unauthenticated route: the RFC 9728 metadata endpoint /.well-known/oauth-protected-resource is also served without a credential. Every other route, /metrics and the JSON-RPC endpoint included, is wrapped in requireAuth.
Resources default to 100m/64Mi requests and 200m/128Mi limits — enough for the collectors, which are short-lived readers, not a steady workload.
hostPaths.proc and hostPaths.sys mount the node's /proc and /sys read-only at /host/proc and /host/sys with type: Directory, and set HOST_PROC/HOST_SYS to match. Be aware that no collector reads those variables: the Linux collectors use absolute /proc paths, so a pod sees the node's non-namespaced /proc files (CPU, memory, load) through its own mount and sees only its own PID namespace for process-level tools. Set hostPaths.*.enabled=false if your PodSecurity admission policy forbids hostPath volumes; you lose nothing that the collectors currently use. The same caveat is explained in Docker and Compose.
A ServiceAccount is created by default and no Role or RoleBinding is rendered, because the pod never calls the Kubernetes API. Set serviceAccount.create=false with serviceAccount.name to reuse an existing identity — useful when you bind a cloud workload identity to it.
Deployment or DaemonSet
The chart renders a Deployment only. The HPA (autoscaling.enabled) names it in scaleTargetRef, and the PDB (podDisruptionBudget.enabled) selects its pods by the chart's selector labels. That is the right shape when Secronyx is a shared diagnostics endpoint for the cluster and you mostly call tools that describe the node's kernel, packages and configuration.
It is the wrong shape when you want per-node coverage. With replicaCount: 1 the pod lands on one node and every answer describes that node; with several replicas the Service load-balances between nodes, so two identical calls can describe two different machines. The chart has no DaemonSet template. If you need one pod per node, write a DaemonSet that reuses the same container spec, give each pod its node name with the downward API, and address pods individually rather than through a single ClusterIP:
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeNameFor a fleet that spans clusters and bare metal alike, the outbound agent is usually a better answer than a per-node listener: see Fleet deployment and Hybrid and SaaS mode.
Network policy
The chart ships no NetworkPolicy. The Service is ClusterIP by default, which in a cluster with no policy engine still means any pod can reach port 8080. Apply your own default-deny plus an explicit allow:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: secronyx
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: secronyx
app.kubernetes.io/instance: secronyx
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ai-ops
podSelector:
matchLabels:
app: orchestrator
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
ports:
- protocol: TCP
port: 8080Remember that kubelet probes /health from the node, which most CNIs exempt from policy; verify on your own CNI rather than assuming.
Prefer an Ingress with TLS over service.type: LoadBalancer. ingress.enabled renders a standard networking.k8s.io/v1 Ingress from ingress.hosts and ingress.tls; set ingress.className and any controller annotations yourself.
Metrics
With metrics.enabled (true by default), metrics.serviceMonitor.enabled=true renders a monitoring.coreos.com/v1 ServiceMonitor that scrapes the http port at /metrics every metrics.serviceMonitor.interval (30s by default). Note that /metrics is wrapped in the same authentication as the MCP endpoint, and the rendered ServiceMonitor carries no authorization or bearerTokenSecret stanza — with authentication enabled, scrapes will be answered with 401. Patch the ServiceMonitor to reference the same Secret:
endpoints:
- port: http
path: /metrics
interval: 30s
authorization:
type: Bearer
credentials:
name: secronyx-token
key: tokenSeries and labels are listed in Remote access over HTTP.
Verifying an install
NOTES.txt prints the exact commands for your values, including the scheme, which follows mcp.tls.enabled — use https:// below when the pod terminates TLS itself. In short:
kubectl port-forward svc/secronyx 8080:8080
curl http://127.0.0.1:8080/health
export TOKEN=$(kubectl get secret secronyx-token -o jsonpath='{.data.token}' | base64 -d)
curl -X POST http://127.0.0.1:8080/ \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'If tools/list is shorter than you expect, check mcp.scopes before checking anything else — an unregistered tool is invisible, not forbidden.
Built 2026-09-19. Source: levantar-ai/secronyx. Found a mistake? Tell us.