Documentation menu

Helm values

Every value the stackradar-scanner Helm chart accepts, with type, default and what it does: credentials, namespace and image filters, resources, proxy, NetworkPolicy and RBAC.

Every value the stackradar-scanner chart accepts, generated from chart 0.4.0. Only the two credentials are required; everything else has a working default. The chart ships a JSON schema, so a mistyped key fails helm install by name instead of being silently ignored.

Setting values

Keep anything beyond the credentials in a values file — it can live in git and makes upgrades repeatable. Pass the API key at deploy time, or not at all with stackradar.existingSecret.

my-values.yaml
scanner:
  excludeNamespaces: "kube-system,kube-public,kube-node-lease,monitoring"
  excludeImages: "registry.k8s.io/*"

watcher:
  resources:
    limits:
      memory: 2Gi
bash
helm upgrade --install stackradar-scanner \
    oci://ghcr.io/lockdep/charts/stackradar-scanner \
    --namespace stackradar --create-namespace \
    --set stackradar.clusterId=$STACKRADAR_CLUSTER_ID \
    --set stackradar.apiKey=$STACKRADAR_API_KEY \
    --values my-values.yaml
To see the values of the exact chart version you run, ask the chart itself: helm show values oci://ghcr.io/lockdep/charts/stackradar-scanner --version <version>.

Credentials and API endpoint

How the scanner identifies itself to StackRadar. Set stackradar.clusterId and stackradar.apiKey inline, or deliver both in a Secret and name it in stackradar.existingSecret. See API keys and cluster scoping.

stackradar.apiUrl

string · default "https://api.stackradar.io"

StackRadar API endpoint. Override only when running against a self-hosted instance.

stackradar.clusterId

string · default ""

Cluster ID to identify this cluster in StackRadar. Can be passed inline at install time instead of via a Secret (avoid in day-to-day use — values land on disk and in CI logs).

stackradar.apiKey

string · default ""

API key for StackRadar. If set, the chart creates the credentials Secret automatically.

stackradar.existingSecret

string · default ""

Name of a pre-existing Secret with keys cluster-id and api-key. When set, the chart skips Secret creation and the Deployment reads from this Secret instead. Compatible with sealed-secrets, external-secrets, and SOPS workflows.

stackradar.existingSecretKeyApiKey

string · default ""

Key within existingSecret holding the API key. Defaults to api-key when empty.

stackradar.existingSecretKeyClusterId

string · default ""

Key within existingSecret holding the cluster ID. Defaults to cluster-id when empty.

What gets scanned

Namespace and image filters, private registry credentials, and the optional cluster reads that attribute a workload to its Helm release or ArgoCD application. Registry authentication is covered end to end in Private registries.

scanner.excludeNamespaces

string · default "kube-system,kube-public,kube-node-lease"

Comma-separated list of namespaces to exclude from scanning.

scanner.includeNamespaces

string · default ""

Comma-separated list of namespaces to scan. When set, ONLY these namespaces are scanned.

scanner.excludeImages

string · default ""

Comma-separated glob patterns for images that are never scanned, whatever namespace they run in. Matched against the image name with its tag and digest stripped, so a pattern written once keeps working as the tag moves. * is the only wildcard and it spans /; everything else is literal, and patterns match the whole name — registry.k8s.io/* covers registry.k8s.io/pause:3.9 but not myregistry.io/registry.k8s.io-mirror/app. A pattern that carries a :tag or an @sha256:... could never match, so it is dropped with a warning at startup rather than silently doing nothing. The agent logs the patterns it ended up with when it starts — worth reading, since an over-broad one drops coverage quietly. Example: "registry.k8s.io/*,*/pause,ghcr.io/acme/vendor-*"

scanner.syftTimeoutMs

integer · default 300000

Timeout per image scan in milliseconds.

scanner.skipExistingDigests

boolean · default true

Skip images whose digest is already indexed in StackRadar. Eliminates redundant syft runs when the image binary has not changed.

scanner.resolveImagePullSecrets

boolean · default false

Resolve imagePullSecrets from workload pod specs for private registry auth. Off by default: it needs secrets get in the ClusterRole, and the chart refuses to grant that cluster-wide — turning this on also requires naming the pull Secrets in scanner.imagePullSecretNames, which becomes an RBAC resourceNames restriction. Leave it off if your images are public, or if you mount one static pull secret via dockerConfigSecret.

scanner.resolveWorkloadOwners

boolean · default true

Read the metadata of the object each pod belongs to — its Deployment, StatefulSet, DaemonSet, Job or CronJob — to recover the Helm and GitOps context the pod itself does not carry. Requires get on apps and batch in the ClusterRole, which this value gates.

On by default because it fixes wrong data rather than adding capability: Helm writes meta.helm.sh/release-* onto the objects it applies and never onto the pod template inside them, and an operator that generates a StatefulSet from a custom resource writes its own labels for the pods it manages — so with pod metadata alone, chart-managed workloads are reported as unmanaged. The agent asks for PartialObjectMetadata, so the API server returns labels and annotations and never the workload spec, and only the same allowlist that applies to pod metadata is ever forwarded.

One get per distinct controller per process, on the 5-minute inventory path — never on the pod watch. Denied (403) it logs one line and falls back to pod-only attribution rather than failing the report.

scanner.resolveArgocdApplications

boolean · default true

Read ArgoCD Application objects for the repository URL, chart and target revision behind a GitOps-delivered workload. Requires list on argoproj.io/applications in the ClusterRole, which this value gates.

Harmless on a cluster with no ArgoCD: the first list returns 404, which the agent takes as "no CRD" and never asks again. Workloads still get their delivery layer from the argocd.argoproj.io/tracking-id annotation without it — they simply carry no repository URL, and a chart upgrade then has no targetRevision to point at.

scanner.imageMetadata

boolean · default true

Send image metadata beside each SBOM: the image's ordered layer list with the (redacted) Dockerfile line that made each layer, and a fixed set of facts from the image config and manifest — base-image annotations, OCI source/version labels, architecture, the default user, the executable the image starts, and environment keys ending _VERSION. It is what lets StackRadar tell base-image findings from the ones your own build added and recommend a newer base.

Every property is listed by name under "What leaves your cluster" in the README. Secrets are masked in the agent before anything is sent, command arguments and all other environment variables never are sent. Set to false and the upload is syft's CycloneDX document and nothing else; findings and scores are unaffected, base-image attribution is not available.

scanner.argocdNamespace

string · default "argocd"

Namespace holding your ArgoCD Application objects. Only used when scanner.resolveArgocdApplications is "true".

Also the namespace reported for an app named by a tracking annotation, since Argo's tracking ID names the app but not where it lives. With apps-in-any-namespace enabled the annotation carries <namespace>/<app> and that wins over this value.

scanner.imagePullSecretNames

list · default []

Names of the imagePullSecrets the scanner is allowed to read. Required when scanner.resolveImagePullSecrets is "true": the ClusterRole grants secrets get only on these names via RBAC resourceNames, and an empty list fails the render rather than granting every Secret in the cluster. Names match in every namespace: a Secret called regcred anywhere in the cluster is readable, so use distinct names if that matters to you. A Secret left off the list is not readable, and images that need it fall back to an anonymous pull — the agent logs a warning naming the Secret.

dockerConfigSecret

string · default ""

Name of a kubernetes.io/dockerconfigjson Secret to mount as a docker config, allowing syft to pull from private registries. Create with: kubectl create secret generic registry-credentials --from-file=.dockerconfigjson=...

Watcher and resources

The scanner runs as a single Deployment that watches pods and sweeps the cluster on an interval. These values size it.

watcher.enabled

boolean · default true

Enable the event-driven watcher with periodic sweeps.

watcher.sweepIntervalMs

integer · default 21600000

How often to run a full pod sweep in milliseconds. Set to 0 to disable periodic sweeps and rely solely on the informer.

watcher.concurrentScans

integer · default 1

Maximum number of concurrent syft scans. Each invocation can use 300–600 MiB for large images; keeping this at 1 avoids OOM kills.

watcher.healthPort

integer · default 8081

Port for the agent's liveness and readiness endpoints. Not exposed by a Service — the kubelet reaches it on the pod IP and nothing else does. Change it only if something else in the pod's network namespace already listens here. Must be 1024 or above: the container drops every Linux capability, so it cannot bind a privileged port.

watcher.scratchSizeLimit

string · default 4Gi

Size limit of the scratch volume mounted at /tmp, where syft unpacks each image's layers before cataloguing them. Without a limit one very large image fills the node's disk and the kubelet evicts *other* pods under DiskPressure; with one, the scanner pod is evicted instead and restarts with an empty volume. Set it above the compressed size of your largest image.

watcher.resources

object · default:

yaml
requests:
  cpu: 50m
  memory: 512Mi
  ephemeral-storage: 1Gi
limits:
  memory: 1Gi
  ephemeral-storage: 6Gi

Resource requests and limits for the watcher Deployment. No CPU limit is set by default — CPU is compressible, and a limit only throttles syft mid-scan on a node that has cycles to spare. Clusters that enforce LimitRanges or quotas can set one with watcher.resources.limits.cpu. The ephemeral-storage limit bounds /tmp plus container logs; keep it above watcher.scratchSizeLimit.

Proxy, CA bundle and NetworkPolicy

For clusters with restricted egress or a TLS-intercepting proxy. See Restricted egress for the destinations the scanner needs.

proxy.httpProxy

string · default ""

HTTP proxy URL for outbound traffic, e.g. http://proxy.corp:8080. Include the scheme — a bare proxy.corp:8080 is rejected, because tools that read these variables disagree about what a scheme-less value means.

proxy.httpsProxy

string · default ""

HTTPS proxy URL for outbound traffic. Set this one if you set only one: the StackRadar API and most registries are reached over https.

proxy.noProxy

string · default ""

Comma-separated hosts that bypass the proxy. The in-cluster Kubernetes API (kubernetes.default.svc, .svc, .cluster.local, localhost, 127.0.0.1) is always appended, so list only your own internal registries here.

caBundle.configMapName

string · default ""

Name of a ConfigMap holding additional CA certificates to trust, for clusters behind a TLS-intercepting proxy. The object must hold a PEM bundle under caBundle.key; it is mounted read-only and added to the trust store of both runtimes in the image. Mutually exclusive with caBundle.secretName — setting both fails the render rather than silently picking one.

caBundle.secretName

string · default ""

Name of a Secret holding the same thing, for workflows that keep the bundle in a Secret. A CA certificate is public by nature, so a ConfigMap is the usual home; this exists because external-secrets and SOPS pipelines deliver everything as Secrets.

caBundle.key

string · default "ca-certificates.crt"

Key within the ConfigMap or Secret holding the PEM bundle. Only this one key is mounted, so unrelated keys in the same object are not exposed to the pod.

networkPolicy.enabled

boolean · default true

Create a NetworkPolicy for the scanner pod. Ingress is denied except the health port the kubelet probes on; egress is allowed to the destinations in networkPolicy.egress — by default DNS, the Kubernetes API server and TCP 443 (the StackRadar API and your registries). On by default so the agent's reach is an object you can read, not a promise. Requires a CNI that enforces NetworkPolicy — with one that does not, the object is inert rather than a false sense of safety. Set to false if a registry on a non-443 port stops being scanned and you would rather not write an egress rule for it.

networkPolicy.egress

list · default []

Egress rules, written in the API's own spec.egress shape. Left empty, the chart renders defaults covering DNS, the Kubernetes API server, and TCP 443 to any address — the StackRadar API and your registries. A list set here replaces those defaults entirely rather than adding to them: narrow the 443 rule to the CIDRs your registries live in if you know them, and keep a DNS rule of your own or nothing resolves. Behind a proxy this collapses to DNS plus the proxy's address, which is a far tighter policy than the default.

Scanner image

Where the scanner's own image is pulled from — change these when you mirror the chart and image into an internal registry.

image.repository

string · default ghcr.io/lockdep/stackradar-scanner

Container image repository.

image.tag

string · default ""

Image tag. Defaults to the chart appVersion, which for a released chart is the release version — leave empty so the chart and the image it deploys stay in lockstep.

image.digest

string · default ""

Image digest (sha256:...). Stamped by the release workflow so every published chart pins the exact image bytes it was tested against, and set empty on dev builds. The digest wins over the tag: to run a different image you must clear this as well (--set image.tag=X --set image.digest=""), otherwise the tag is cosmetic.

image.pullPolicy

string · default Always

Image pull policy.

imagePullSecrets

list · default []

List of image pull secrets for the scanner pod.

RBAC and ServiceAccount

The scanner's read access, and the ServiceAccount annotations that wire up cloud workload identity on EKS and GKE.

rbac.create

boolean · default true

Create the ClusterRole and ClusterRoleBinding that give the ServiceAccount its read access. Independent of serviceAccount.create: a cluster that manages ServiceAccounts elsewhere still usually wants the chart's RBAC, and the old coupling meant serviceAccount.create=false silently rendered no rules and a pod that 403'd on its first list. Set to false only if you bind the documented rules yourself.

serviceAccount.create

boolean · default true

Create a ServiceAccount for the scanner.

serviceAccount.name

string · default ""

Override the ServiceAccount name. Defaults to the release name when empty.

serviceAccount.annotations

object · default {}

Annotations for the ServiceAccount. This is where cloud workload identity is wired up — eks.amazonaws.com/role-arn on EKS, iam.gke.io/gcp-service-account on GKE — so the pod receives short-lived credentials from the cloud instead of a static dockerConfigSecret. Only meaningful with serviceAccount.create: true; setting both fails the render rather than putting the annotations nowhere.

Pod, scheduling and naming

Standard Kubernetes knobs, passed through to the scanner pod.

podLabels

object · default {}

Extra labels for the scanner pod. Azure Workload Identity is switched on here, with azure.workload.identity/use: "true". The chart's own labels win on a collision: app.kubernetes.io/name, app.kubernetes.io/instance and app.kubernetes.io/component are part of the Deployment's selector and are immutable after creation, so a value that displaced one would break the next helm upgrade rather than this install.

podAnnotations

object · default {}

Extra annotations for the scanner pod, e.g. the client ID Azure Workload Identity reads (azure.workload.identity/client-id).

podSecurityContext

object · default:

yaml
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
fsGroup: 65534
seccompProfile:
  type: RuntimeDefault

Pod-level security context. seccompProfile: RuntimeDefault is what a namespace enforcing the restricted Pod Security Standard requires; without it the pod is rejected at admission.

securityContext

object · default:

yaml
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
  drop:
    - ALL

Container-level security context.

nodeSelector

object · default {}

Node selector for pod scheduling.

tolerations

list · default []

Tolerations for pod scheduling on tainted nodes.

affinity

object · default {}

Affinity rules for fine-grained pod placement.

priorityClassName

string · default ""

PriorityClassName so the scanner yields resources to application pods.

nameOverride

string · default ""

Override the chart name used in resource names and labels.

fullnameOverride

string · default ""

Override the full name of the chart's resources, replacing the <release>-<chart> default entirely.

Next steps