Documentation menu

Private registries & restricted networks

Run the StackRadar scanner behind private registries or restricted egress: registry credentials, chart and image mirroring, proxies, and the endpoint to allow.

Many clusters can't pull from public registries or have tightly filtered egress. This guide covers the four patterns that make the StackRadar scanner work there: giving it credentials for your private registries, mirroring the chart and image into your own registry, routing through an egress proxy, and allowlisting the one endpoint the scanner needs.

StackRadar's dashboard is a managed service, so a fully air-gapped cluster (zero outbound connectivity, ever) can't report scan results. The scanner needs one outbound path: HTTPS to api.stackradar.io. Everything else — the chart, the image, all image pulls — can come from inside your network.

What the scanner needs, exactly

RequirementCan be internal?
Helm chart (stackradar-scanner)Yes — mirror to any OCI registry or internal chart repo
Scanner container imageYes — mirror to your private registry
Read access to the registries your workloads run fromYes — the scanner pulls images over your own network, never through StackRadar
SBOM uploads & heartbeats — HTTPS to api.stackradar.ioNo — allowlist directly or route via egress proxy

Notably, the scanner does not need to download a vulnerability database into your cluster. Vulnerability matching happens server-side against StackRadar's continuously synced OSV.dev mirror, so there is no nightly feed download to allowlist and no stale in-cluster database to maintain. See how vulnerability matching works.

Authenticating to a private registry

The scanner pulls every image it scans itself, from the registry, rather than reading layers off the node. So for any workload running from a private registry it needs credentials of its own. This is what the cluster page's coverage card means when it shows “Registry authentication failed” naming a registry host: the registry rejected the scanner's pull, and those images will never get an SBOM until it has credentials — waiting does not fix it. (A “rate-limited, retrying” row is different: that one heals on its own and needs no credentials.) There are two ways the scanner gets credentials, and they combine. Once configured, the next scan uploads and the error clears on its own — no manual reset.

Reusing the pull Secrets your workloads already have

When a pod declares imagePullSecrets, the scanner can read those Secrets in the pod's own namespace and use them to pull that pod's images. It is off by default, because it costs one permission — secrets get in the scanner's ClusterRole — and the chart will not grant that on every Secret in the cluster. Turning it on means naming the pull Secrets it may read, which the chart translates into an RBAC resourceNames restriction; leave the list empty and the install fails rather than rendering a cluster-wide grant.

Find the Secret names on the pods that pull from the private registry. Ask the pods, not the Deployment or CronJob: when the Secret is attached to a ServiceAccount (DigitalOcean's registry integration does this), Kubernetes copies it onto each pod at creation and the controller's template stays empty.

bash
kubectl get pods -n <namespace> -o jsonpath='{range .items[*]}{.metadata.name}: {.spec.imagePullSecrets[*].name}{"\n"}{end}'

If the pods are gone — a finished Job — read the ServiceAccounts in that namespace instead:

bash
kubectl get sa -n <namespace> -o jsonpath='{range .items[*]}{.metadata.name}: {.imagePullSecrets[*].name}{"\n"}{end}'
bash
helm upgrade stackradar-scanner oci://ghcr.io/lockdep/charts/stackradar-scanner \
    --namespace stackradar --reuse-values \
    --set-string scanner.resolveImagePullSecrets=true \
    --set 'scanner.imagePullSecretNames={regcred,ghcr-creds}'

Two things to know before you set it. Names match in every namespace — a Secret called regcred anywhere in the cluster becomes readable — so use distinct names if that distinction matters to you. And a Secret left off the list is simply denied: images that needed it fall back to an anonymous pull, and the scanner logs a warning naming the Secret and its namespace rather than failing the sweep.

Credentials your cluster holds outside a pull Secret are not visible to the scanner. If your pods pull from ECR, Artifact Registry or ACR using the node's own identity — no imagePullSecrets on the pod at all, which is the common setup on a managed cluster — there is nothing for the scanner to reuse, and those images fall back to an anonymous pull that fails. Give it a credential of its own, below.

Giving the scanner its own credential

dockerConfigSecret points the scanner at a standard Docker config Secret, mounted read-only. It covers every pull that the pod's own imagePullSecrets did not; where both apply to the same registry, the workload's credential wins.

bash
kubectl create secret docker-registry stackradar-registry \
    --namespace stackradar \
    --docker-server=<registry-host> \
    --docker-username=<username> \
    --docker-password=<password>

helm upgrade stackradar-scanner oci://ghcr.io/lockdep/charts/stackradar-scanner \
    --namespace stackradar --reuse-values \
    --set dockerConfigSecret=stackradar-registry

The credential should be read-only and scoped to the repositories you want scanned. Note that on ECR a registry password is a 12-hour token, so a static Secret there needs a refresh job of your own — a robot account with a long-lived credential, where your registry offers one, is the easier path.

Cloud workload identity

On EKS, GKE and AKS the idiomatic alternative to a static credential is giving the scanner's ServiceAccount a cloud identity with read access to your registry, so the pod receives short-lived credentials from the cloud instead. The chart exposes the hooks each cloud's mechanism expects:

  • EKS (IRSA) — annotate the ServiceAccount with serviceAccount.annotations."eks.amazonaws.com/role-arn", pointing at a role with ECR read access.
  • GKE (Workload Identity) serviceAccount.annotations."iam.gke.io/gcp-service-account", bound to a Google service account with Artifact Registry reader.
  • AKS (Azure Workload Identity) — set podLabels."azure.workload.identity/use": "true" and the client ID via podAnnotations."azure.workload.identity/client-id", for an identity with AcrPull.

The per-platform install guides carry the full setup for each; where workload identity is not an option, dockerConfigSecret above is the fallback known to work everywhere.

Not to be confused with the chart's imagePullSecrets value, which is how the kubelet pulls the scanner's own image when you have mirrored it into a private registry. That one is about starting the scanner; the two above are about what the scanner can scan once it is running.

Mirroring the chart and image

Check the current chart version and default values on Artifact Hub, then pull the chart from the public OCI registry on a connected machine:

bash
helm pull oci://ghcr.io/lockdep/charts/stackradar-scanner --version <version>

Pin --version — for a mirror, reproducibility is the point: the .tgz you push is then exactly the bytes you reviewed, not whatever the registry considered latest that day. Push it to your internal registry (any OCI-compliant registry works — Harbor, ECR, Artifact Registry, ACR, Nexus):

bash
helm push stackradar-scanner-<version>.tgz oci://registry.internal.example.com/charts

Mirror the scanner image the same way you mirror other public images (e.g. with crane copy, skopeo copy, or your registry's proxy-cache feature). One constraint: released charts pin the scanner image by digest (image.digest), so your mirror must hold a byte-identical copy — crane copy and skopeo copy preserve digests, as does a proxy-cache. If you rebuild or re-sign the image, its digest changes, and you must clear image.digest (and set image.tag) — the digest wins over the tag, so without clearing it the pull asks your registry for bytes it doesn't hold and fails. List the chart's default image references and configurable values with:

bash
helm show values oci://ghcr.io/lockdep/charts/stackradar-scanner

Then install from your internal registry, overriding the image repository values shown by helm show values to point at your mirror:

bash
helm install stackradar-scanner \
    oci://registry.internal.example.com/charts/stackradar-scanner \
    --namespace stackradar --create-namespace \
    --set stackradar.apiKey=$STACKRADAR_API_KEY \
    --set stackradar.clusterId=$STACKRADAR_CLUSTER_ID \
    --values internal-registry-overrides.yaml

In these environments credentials rarely belong on the command line at all. If a sealed-secrets, external-secrets, or SOPS pipeline delivers your Secrets, give it a Secret with cluster-id and api-key keys and point the chart at it with stackradar.existingSecret instead of the two --set flags above — the chart then creates no credentials Secret of its own.

Restricted egress

  • Firewall allowlist — allow outbound HTTPS (TCP 443) to api.stackradar.io. No other StackRadar endpoint is contacted by the scanner.
  • Egress proxy — set proxy.httpsProxy (and proxy.noProxy for your internal registries) and the chart puts HTTPS_PROXY / NO_PROXY on the pod and routes the agent's own API calls through the proxy too. Include the scheme — proxy.corp:8080 is rejected at install time. The in-cluster Kubernetes API is always exempted, so you only list your own hosts. If the proxy terminates TLS, add your CA with caBundle.configMapName — or caBundle.secretName when your secrets pipeline delivers the bundle as a Secret.
  • NetworkPolicy — the chart ships its own, on by default (networkPolicy.enabled): a policy allowing egress to DNS, the Kubernetes API server, and TCP 443 (the StackRadar API and your registries), and nothing else — networkPolicy.egress replaces those defaults when you want to narrow the 443 rule to known CIDRs, or add a registry on another port. The flip side under default-deny ingress: the kubelet must still reach the scanner's health port, or its probes fail and the pod restarts in a loop — the chart's policy already allows that ingress, so prefer it over hand-writing one.

What actually leaves the network

Uploads are CycloneDX SBOM documents: package names, versions, and identifiers (PURLs) for each container image, plus cluster/workload metadata so results are attributable. No source code, no environment variables, no secrets, and no image contents are transmitted — and all data is stored in the EU. Details in architecture & data flow.

Next steps