Skip to main content
Deploy Radar to your Kubernetes cluster for shared team access.
Note: This guide covers deploying Radar as a pod in your cluster. If you’re running Radar locally but need to understand cluster connection behavior (e.g., using KUBECONFIG to override in-cluster detection), see the Configuration Guide.
Choose the path that matches what you are trying to do:
  • New installation: follow Quick Start, then configure how your team will reach and authenticate to Radar.
  • Existing installation: start with Upgrading. The correct workflow depends on whether Helm, Argo CD, Flux, or another system owns the deployment.

Quick Start

Access via port-forward:

Installation settings

Shared OSS Radar is configured by the installation operator, not by visitors to its web UI. Settings shows effective configuration and connection status; it does not save installation changes. Use Helm values (or flags/environment for a non-Helm deployment), then restart or roll out Radar. Kubernetes actions such as scaling workloads still follow their existing permissions. For example, add this to your existing values file:
audit: null uses Radar’s default system-namespace exclusions. Explicit empty lists include every namespace and enable all checks. Check IDs are listed in the checks object returned by GET /api/audit. Namespace exclusions accept exact names or one leading/trailing *. OCI entries are prefixes, not full chart references; private-registry credentials are not managed by these values. The chart renders audit/OCI settings into a read-only ConfigMap, separate from Radar’s writable local files. Its checksum triggers a rollout when values change. Radar reads it at startup and rejects unknown fields, unsupported schema versions, invalid patterns, and unknown check IDs. It never writes ConfigMaps or Secrets to save UI edits. Rotating an externally managed integration Secret also requires restarting Radar; that Secret’s contents are not part of the ConfigMap checksum. A timeline PVC does not persist installation settings. When upgrading from an installation where Settings allowed edits, copy the intended configuration into Helm values first. Old Pod-local edits are not automatically adopted, and generally did not survive Pod replacement anyway. Use a chart and binary from the same release: pinning an older image.tag does not add this configuration behavior to the older binary. Future document keys unsupported by a pinned binary fail startup rather than being silently ignored. Theme and sidebar pins stay in each browser; they do not change another visitor’s preferences. Namespace browsing remains available. Authenticated users have their existing per-user namespace filters; without authentication, visitors still share the anonymous namespace selection. A forced --namespace-scope cache cannot be changed by shared-OSS visitors. Context switching and CAPI “Connect to Cluster” are also installation-owned. Live Traffic source selection remains an existing runtime control, not a persisted setting. This is configuration ownership, not authentication. An unauthenticated installation still gives visitors its configured Kubernetes access; configure authentication before sharing it outside a trusted network. The same read-only policy applies to authenticated OSS on a laptop, and to a Pod using an explicit kubeconfig with a non-loopback listener. A personal loopback CLI in a Kubernetes-hosted development workspace remains editable unless an operator file is supplied. Helm installations always supply that file, regardless of listener or kubeconfig overrides. Local unauthenticated CLI/Desktop settings remain editable. Radar Cloud retains its existing role-managed settings and personal preferences; the chart rejects non-default audit or helm.ociSources values in Cloud mode rather than silently ignoring them. For non-Helm shared OSS, use the existing flags/env for integrations. Structured audit/OCI settings can be supplied through a read-only JSON file selected by RADAR_OPERATOR_SETTINGS_FILE: Without that file, managed OSS uses the built-in audit policy and an empty OCI source list, not old values from settings.json. If audit is supplied, both arrays are required; use [] deliberately to remove exclusions or disabled checks.
This file contains no credentials, is read once at startup, and opts the process into operator-owned settings. In Cloud it is ignored with a startup warning.

Upgrading

Upgrade Radar through the same source that manages the current installation. If Argo CD or Flux owns Radar, change the version in Git and let the controller reconcile it. Running helm upgrade directly against a GitOps-managed release creates drift and the controller may revert it. Before upgrading, review the release notes between your current and target versions, especially for a major-version update. The Radar version shown on the Home page matches the published Helm chart version.

Helm

If Radar links you to a Helm release, use that release name and namespace. You can also find likely releases with:
Update the chart repository, then upgrade while preserving the values already set on the release:
Replace radar with the release name and namespace used by your installation. --reset-then-reuse-values requires Helm 3.14 or newer. It starts from the target chart’s defaults, then reapplies the release’s existing values, so keys introduced by the new chart are not omitted. With an older Helm client, upgrade Helm or use the installation’s maintained values file; plain --reuse-values is not equivalent and can omit new defaults. If the deployment is managed from a values file, use that same file with -f values.yaml instead of --reset-then-reuse-values so Git remains the reproducible source of configuration. The command installs the latest published chart; add --version X.Y.Z to pin the exact version shown in Radar (without the leading v).

Argo CD or Flux

Do not upgrade the live Helm release or Deployment directly. Update the version in the repository that owns the installation, then reconcile its controller:
  • Argo CD Application: if the Application directly references the Radar chart, update its Helm chart targetRevision. Otherwise update the chart or image version in the Git source referenced by the Application, then sync it.
  • Flux HelmRelease: update the Radar chart version in the HelmRelease source, usually spec.chart.spec.version, then reconcile the HelmRelease.
  • Flux Kustomization: update the Radar chart or image version in the Git source referenced by the Kustomization, then reconcile it.
Radar links directly to a verified owning Application, HelmRelease, or Kustomization when it can. Open that object to confirm its source and health before changing Git.

If the manager could not be identified

Inspect the Radar Deployment before choosing an upgrade method:
Look at the Deployment’s labels and annotations:
  • meta.helm.sh/release-name and meta.helm.sh/release-namespace identify the underlying Helm release.
  • argocd.argoproj.io/tracking-id or argocd.argoproj.io/instance points to Argo CD ownership.
  • helm.toolkit.fluxcd.io/* and kustomize.toolkit.fluxcd.io/* point to Flux ownership.
A GitOps-managed chart normally has both Helm and GitOps metadata. In that case, GitOps is the source of truth: update Git rather than running Helm directly. If none of these markers exist, update the manifests or image tag in the system that originally deployed Radar.

Verify or roll back

Wait for the Radar Deployment to finish rolling out, then refresh Radar and confirm the new version on the Home page:
Use the actual Deployment name and namespace if they differ. For a Helm-managed installation, inspect helm history and use helm rollback if the rollout fails. For a GitOps-managed installation, revert the version change in Git and reconcile the owning object.

Exposing with Gateway API

If your cluster uses Gateway API, the chart can create an HTTPRoute:
With no custom rules, the chart routes / to Radar. httpRoute and ingress are mutually exclusive, and at least one parentRefs entry is required. See the chart reference for custom rules and timeouts.

Exposing with Ingress

Ingress without authentication

Warning: Only use this behind a trusted private network boundary. Anyone who can reach Radar can use the permissions granted to its ServiceAccount. For shared or externally reachable installations, configure authentication before exposing the ingress.

Subpath Ingress (No Strip-Prefix)

If your ingress forwards /radar/... to the Radar service as /radar/..., set basePath to the same prefix:
Then open https://tools.your-domain.com/radar/. Do not set basePath when your ingress already rewrites /radar to /. Path segments accept letters, digits, -, _, . and ~. Radar serves the app only under the prefix - requests to unprefixed paths get a 404, so update any external health checks or scrapers that hit /api/health or /metrics directly (the chart’s own probes follow basePath automatically). basePath is not supported together with Radar Cloud (--cloud-url), which owns the URL path itself. Every URL you hand to an external system must include the prefix. With OIDC that means the values you register with your identity provider:
Radar’s callback route lives at {basePath}/auth/callback, so a redirect URL without the prefix sends the IdP to a path the ingress doesn’t route to Radar and login ends in a 404. Give each instance its own hostname. Two Radars behind subpaths on one hostname (/radar-a, /radar-b) are the same browser origin, so they share browser state: the session cookie is set at Path=/, and localStorage - which holds the theme, log-viewer preferences and similar per-instance settings - is scoped per origin with no path-scoped equivalent in the platform at all. Logging into one can end the other’s session, and preferences set in one show up in the other. A hostname each keeps them properly separate. Separately from that: a Radar per cluster gives you a view per cluster. Each watches only its own cluster and carries its own upgrades, ingress and auth config, with no cross-cluster search or combined issue list across them. If you want several clusters in one view, that is what Radar Cloud is for. You still run Radar in each cluster, but it dials out to the control plane, so there is no per-cluster ingress to wire up.

With ingress basic authentication

  1. Create the auth secret:
  2. Configure ingress:
  3. Deploy:

With TLS (HTTPS)

Requires cert-manager installed in your cluster.

DNS Setup

  1. Get your ingress IP:
  2. Create a DNS A record pointing your domain to the ingress IP.
Multi-cluster naming convention:
Example: radar.prod-us-east1.example.com

RBAC

Radar uses its ServiceAccount to access the Kubernetes API. The Helm chart creates a ClusterRole with read-only access to common resources by default:
  • Pods, Services, ConfigMaps, Events, Namespaces, Nodes, ServiceAccounts, Endpoints
  • Deployments, DaemonSets, StatefulSets, ReplicaSets
  • Ingresses, NetworkPolicies, Jobs, CronJobs, HPAs, PVCs
  • Kubernetes 1.37 Workloads, PodGroups, CompositePodGroups, PodCertificateRequests, and ClusterTrustBundles when the API server advertises them; the scheduling APIs are feature-gated
  • Pod logs (enabled by default)

Opt-in Permissions

Some features require additional permissions. Most are disabled by default for security:
Node management (cordon, uncordon, drain) is available via the MCP server and API. These operations require patch on nodes, list on pods, and create on pods/eviction, which are not included in the default ClusterRole. The read-only drain plan (POST /api/nodes/{name}/drain-plan) needs get on nodes, list on pods and list on poddisruptionbudgets; without the last one the plan is still returned, marked as not having evaluated PodDisruptionBudgets. Note the defaults differ when deleteEmptyDirData is omitted from the request body: the drain plan assumes false, while the drain itself (and the MCP manage_node drain) assumes true - API callers should always send the field explicitly. Add the grants via rbac.additionalRules or use per-user authentication so each user’s own RBAC governs node operations.
Enable features as needed:
The terminal’s Debug action launches a throwaway container (ephemeral container on a pod, or a privileged pod on a node) using busybox:latest by default. If the built-in restricted Pod Security Standard rejects the default pod debug container, Radar retries with a restricted-compatible Linux security context using the target/pod non-root UID, or UID 65532 by default, so custom images used in restricted namespaces must work as a non-root user. In air-gapped or private-registry clusters where the default image can’t be pulled, point it at a reachable mirror:
Radar doesn’t attach image-pull secrets to debug containers or pods - ephemeral containers inherit the target pod’s, and node debug pods rely on the default namespace’s ServiceAccount / node registry config - so the image must be pullable without Radar supplying credentials.

CRD Permissions

Radar reads CRDs from many popular tools. Common CRD groups can be toggled individually:
Radar’s NetworkPolicy check (an issue when a policy leaves every pod behind a Service with no allowed inbound traffic) also reads the policies of other engines that can allow what a core NetworkPolicy denies: Cilium, Calico, Antrea (rbac.crdGroups.antrea), EKS VPC CNI (rbac.crdGroups.awsNetworkPolicy) and policy.networking.k8s.io. If one of those APIs is served but Radar can’t read it, the check stays silent for the Services it might cover, so keep these groups enabled. Calico access is read-only (get, list, and watch) for both API groups and is enabled by default. Set rbac.crdGroups.calico=false to disable it. If chart-managed Calico access is disabled, grant only the resources you need through rbac.additionalRules; both groups may be required while a cluster exposes modern and legacy Calico APIs. The chart grants cluster-wide get/list/watch on VictoriaMetrics VMAgent, VMCluster and VMSingle whenever it grants cluster-wide Secrets reads: rbac.secrets: true, rbac.helm: true, auth enabled (auth.mode != none), or cloud mode. These objects can carry inline credentials, so they share the same grant decision as Secrets; there is no separate VictoriaMetrics setting. All other VictoriaMetrics kinds are excluded from this rule. See the chart reference for exclusions and wildcard grants. Radar decides what it can read at startup, so RBAC changes take effect when the Radar pod restarts. helm upgrade rolls the pod whenever rbac values change; restart Radar yourself after changing its RBAC outside Helm.

Graceful RBAC Degradation

You see what you have access to - Radar doesn’t require cluster-admin. Whatever your ServiceAccount (or the impersonated user, when auth is enabled) can list, Radar shows. Resource types you can’t list show an actionable denied-state instead of a misleading “0 / None found”: for a core cluster-scoped kind (Node, PV, StorageClass, and the like) your identity can’t read, Radar shows a copyable ClusterRole + ClusterRoleBinding request to hand to a cluster admin (reason rbac_denied), and distinguishes that from a kind Radar’s own ServiceAccount can’t read, where a user-level grant wouldn’t help (reason unavailable, no snippet). The “Your access on this cluster” dialog lists the core cluster-scoped kinds being hidden alongside your effective rules. Namespaces you can’t access don’t appear. A namespace-scoped ServiceAccount (RoleBinding without a ClusterRole) is fully supported - Radar detects this at startup and works within the permitted namespace. Stalled API requests. If a read from the Kubernetes API stalls without the connection resetting (for example, a proxy whose upstream dropped silently), Radar ends it after 60 seconds with no data and retries, instead of freezing that resource’s data until a restart. Slow transfers that keep making progress are never cut, and log streams, exec and port-forward are unaffected. RBAC granularity (auth enabled):
  • Namespaced resources (Pods, Deployments, Services, …) are filtered by namespace: read access is granted in any namespace where the user has list-pods or list-deployments. Secrets are gated independently - a user sees Secret objects (including Helm release storage) only in namespaces where their own RBAC grants list secrets, even when they have broader access in that namespace. RBAC objects and webhook configurations are likewise re-checked per user. Other cached resource types are visible within a namespace once the user has workload-read access there.
  • Cluster-scoped resources (Nodes, PVs, StorageClasses, ClusterRoles, cluster-scoped CRDs, …) are gated per-kind via SubjectAccessReview. Cluster-wide pod visibility does NOT imply Node visibility - every cluster-scoped read goes through its own RBAC check, with results cached per user.
The same RBAC boundary applies to MCP - read tools intersect with each user’s allowed namespaces, write tools impersonate the user against the apiserver, and cluster-scoped reads run the same per-kind SAR. The pod ServiceAccount’s permissions are the upper bound for both REST and MCP; per-user RBAC narrows that to what each user can see. Example: Namespace-scoped deployment
Set rbac.create: false in the Helm values and apply the custom Role/RoleBinding above. Radar will detect the namespace-scoped permissions and work within my-team only.

Authentication

For shared team access, enable authentication so each user gets per-user permissions via Kubernetes RBAC. See the Authentication & Authorization Guide for full setup instructions. Quick start with proxy auth:
Then deploy an auth proxy (e.g., oauth2-proxy) in front of Radar. Users authenticate through the proxy, and Radar uses K8s impersonation so each user’s actions are governed by their own K8s RBAC bindings. Quick start with OIDC:

Security Considerations

When deploying Radar in-cluster: The Helm chart explicitly sets --listen-address=0.0.0.0 so its ClusterIP Service can reach the pod. Unlike the native CLI’s loopback-only default, this makes Radar reachable on the pod network; the following controls are therefore part of the deployment boundary.
  1. Authentication: Always enable authentication when exposing via ingress. Use built-in auth (proxy or OIDC mode) or basic auth (shown above) at minimum.
  2. RBAC scope: The default ClusterRole grants cluster-wide read access. For namespace-restricted access, set rbac.create: false and create a custom Role/RoleBinding. Radar will gracefully adapt to the available permissions.
  3. Privileged features: Terminal (podExec) and port forwarding grant significant access. Only enable these in trusted environments or when using per-user authentication.
  4. Network access: Consider using NetworkPolicies to restrict which pods can reach Radar.

Timeline Storage: memory vs sqlite vs postgres

Radar’s timeline records every cluster change. Three backends:
  • memory (default): events live in-process, lost on pod restart. Lowest footprint; pick this if you only need recent activity (last few hours).
  • sqlite: events persist to a PVC across restarts. Multi-day audit trail; pick this for long-running in-cluster deployments where you care about history surviving pod cycles. Requires persistence.enabled=true.
  • postgres: events persist in an externally managed PostgreSQL database. Pick this for durability across restarts and rolling updates, or when you want to avoid the single-PVC constraint. Use a dedicated database per Radar deployment. The DSN must be provided via an existing Kubernetes Secret (timeline.postgres.existingSecret); Helm never touches the credential.
For SQLite, tune timeline.retention (Go duration; 0 disables age cleanup), timeline.maxSize, and persistence.size together. Keep timeline.maxSize below the PVC size so Radar prunes oldest events before the volume fills. timeline.maxSize is ignored for postgres and memory. Cleanup runs hourly + once at startup. Confirm it’s keeping up via /api/diagnostics - the timeline.maxStorageBytes, timeline.lastCleanupAt, timeline.lastCleanupDeletedRows, timeline.lastCleanupError, and timeline.storageBytes fields surface the state without requiring kubectl logs.

PostgreSQL requirements

  • PostgreSQL 14+ (tested against 17).
  • A dedicated database and a user with DDL permissions so Radar can create/update the radar_timeline_* tables, indexes, and migrations on first startup.
  • sslmode and other TLS settings are controlled through the DSN string.
  • The DSN is read from a Secret; never put it in Helm values or config.json.
  • Restart the Radar Deployment after rotating the Secret because Kubernetes does not refresh environment variables in running containers.
  • There is no built-in import path from SQLite to PostgreSQL. Migrating historical events is not supported.
  • An unreachable PostgreSQL costs you the timeline, not the rest of Radar. Cluster views keep working, the Timeline page says the database can’t be reached and points to the logs, which have the connection error (so does the diagnostics overlay), and Radar reconnects on its own once the database answers. Radar logs when the database becomes unreachable, how soon it tries again, and when it answers again, naming the database’s host, port, name and user (never the password) with the driver’s error; search the logs for [timeline]. Events that occur while it is down are not recorded and are not recovered later; the dropped count in diagnostics shows how many. Radar never falls back to an in-memory timeline, because that looks healthy and then loses everything on the next restart.
  • Because the rest of Radar keeps working and /api/health stays healthy, an outage is easy to miss. Alert on Radar’s /metrics instead: radar_timeline_available is 0 while the database can’t be reached (checked at most every 5s, with a 2s limit on each check), and radar_timeline_dropped_events_total{reason="store_unavailable"} and {reason="store_failed"} count the changes lost to it. For example, radar_timeline_available == 0 held for 5 minutes. The reason itself is not in the metrics, because it names the database host; it is in the Timeline page and the diagnostics overlay.
  • Verified TLS modes: require and verify-full.

Configuration Reference

See Helm Chart README for all available values. Response compression: Radar gzip-compresses HTTP responses by default (streaming endpoints like SSE are excluded). The level defaults to 1 (best speed), since on large clusters peak response size coincides with peak CPU. Set the RADAR_COMPRESS_LEVEL environment variable (via the chart’s pod env) to 0 to disable, or 2-9 to trade CPU for smaller bodies on bandwidth-bound deployments.

Troubleshooting

Pod not starting

Ingress not working

If the UI loads through an ingress but pod exec does not connect, ensure the ingress forwards WebSocket upgrades. If it does and exec still fails, see the next section: the terminal uses the same address check as changes.

Node drain requests time out

Drain waits for pod deletion by default. The default deadline is two minutes for eviction retries and deletion waiting together, followed by a final verification read of up to five seconds. Configure every proxy and client on this path to allow at least 150 seconds for the default. If you send an explicit drain timeout, allow more than that value plus five seconds and network latency. For ingress-nginx, add these annotations to your Radar Ingress, or set them under the Helm chart’s ingress.annotations values:
These are the ingress-nginx controller’s timeout annotations. Other ingress controllers and load balancers have their own request or upstream response timeout settings. Set each layer explicitly; extending Radar’s deadline cannot override a shorter limit in front of it. A browser, proxy or MCP timeout does not prove the drain failed or that nothing changed. Evictions may have been accepted already. Check the node’s pods before retrying or shutting it down. API callers can use waitForDeletion: false and MCP callers wait_for_deletion: false for admission-only behavior, but must then verify deletion separately. See MCP drain timeouts.

Changes fail with “Radar refused this request”

If you can browse, but every action that changes the cluster (apply, delete, scale, restart, drain, Argo CD or Flux sync) fails with Radar refused this request because it came from …, the proxy in front of Radar is rewriting the Host header. The pod terminal does not connect, and a few views that load data with POST, such as application cost, fail the same way. Radar accepts these only from a page served at its own address, so that another website you have open cannot act through your browser, and a rewritten Host hides that address. The message names both addresses: where the request came from, and the Host Radar received. Fix it either way:
  • Configure the proxy to keep the original Host header.
  • Tell Radar the address you open it at. In the Helm chart:
    Outside Helm, set RADAR_TRUSTED_ORIGINS or pass --trusted-origins. Use the scheme and host exactly as they appear in your browser’s address bar, with the port if there is one. Separate several with commas. A trusted origin can do everything Radar’s own page can: read, make changes and open pod terminals (and, on a local install, the local terminal). List only addresses that serve Radar. This setting does not cover the MCP endpoint at /mcp, which has its own RADAR_MCP_TRUSTED_ORIGINS.
A page served from any other address cannot call Radar’s API at all, reads included. That covers another port on localhost, because browsers treat all ports on a host as one site. If you run your own browser tool that calls Radar’s API, for example a dashboard on http://localhost:3000, add its address to trusted origins the same way. curl, scripts and MCP clients are not affected. This read check relies on the Sec-Fetch-Site header, which browsers send only to HTTPS addresses and to localhost. If you open Radar over plain HTTP at any other address, a page on another site can still make Radar answer a read, though it cannot see the answer. Changes stay protected either way. Serve Radar over HTTPS to close that gap. Radar logs each refusal with the headers it received:

Metrics charts show “Prometheus not connected”

Radar discovers Prometheus in the background as soon as it connects to the cluster (and again after every context switch), by listing Services and probing the ones that look like a PromQL backend at their in-cluster address. When it can watch Services in every namespace it reads them from its own cache instead of listing them from the API. Discovery also runs again in the background, without a timer, when Radar has no connection and a Prometheus-like Service is created or one of its Pods becomes Ready (so a Prometheus installed after Radar connects on its own), and when the connected endpoint stops answering queries or its port-forward closes. While that runs the charts show “Discovering Prometheus…”; once it ends without a connection, the charts and Settings → Overview → Metrics show why:
  • no Prometheus service found in cluster - nothing matched the well-known names or labels. Set traffic.prometheusUrl.
  • No working Prometheus endpoint found. Candidate services could not be reached - candidates exist but their Service addresses did not answer from Radar’s pod. In-cluster Radar does not fall back to port-forwarding. Check endpoint health and network access, or configure a reachable traffic.prometheusUrl. This does not mean Prometheus is absent.
  • Prometheus candidate monitoring/prometheus-server:9090 was unreachable, and NetworkPolicy monitoring/… isolates ingress to its pods with no rule admitting radar/… - Radar found the reason. When a probe fails at the network level, Radar evaluates the core NetworkPolicy objects it can see in its own namespace and the candidate’s against its own pod and the candidate’s published endpoints, and names the policies only when they deny the connection outright (an egress denial on Radar’s side is reported the same way). It stays silent whenever the answer is uncertain: MY_POD_NAME/MY_POD_NAMESPACE unset, a namespace-scoped install that cannot list policies in the candidate’s namespace, an endpoint it cannot resolve, a hostNetwork pod, or a probe that was refused rather than unreachable. Two limits: this is the policy’s declared intent - a CNI that does not enforce NetworkPolicy (kindnet) will still pass the traffic - and Cilium/Calico CRD policies are not evaluated.
  • Prometheus headers are configured but no Prometheus URL is set - see traffic.prometheusHeadersFromEnv above.
For CLI/Desktop connection failures, see local integration connections.

Basic auth prompt not appearing

Verify the secret format:

Uninstalling

If radar is a dedicated namespace and contains nothing you need to retain, delete it separately with kubectl delete namespace radar.