LinearB On-Prem Agent v5 Upgrade Guide
This guide covers all configuration changes required when upgrading the LinearB On-Prem Agent from v4 to v5.
Before upgrading, back up your local-values.yaml and run a helm diff or Argo CD dry-run to preview the changes against your current release.
Breaking changes — action required before upgrading
1. Scheduler worker subcharts removed
v5 moves to native Kubernetes Jobs for processing work. The three Celery-based worker subcharts are removed.
Remove these keys from your local-values.yaml:
# Delete any overrides for these — the subcharts no longer exist
scheduler-worker: ...
scheduler-pm-worker: ...
scheduler-sensors-worker: ...
If your values file has resource tuning (WORKER_REQUEST_CPU, WORKER_LIMIT_MEMORY, etc.) under any of these keys, remove those blocks entirely. Worker pod resource tuning in v5 is done through global.workerPods.
2. Renamed keys
| Old key (v4) | New key (v5) |
|---|---|
global.versions.pods_cleaner | global.versions.job_lifecycle_manager |
minio.DeploymentUpdate | minio.deploymentUpdate |
Update your local-values.yaml wherever these appear.
3. Deprecated concurrency keys
The global job concurrency limit is replaced by per-queue limits.
Remove:
Add:
global:
MAX_GIT_JOBS: "5" # concurrent git (code analysis) jobs
MAX_PM_JOBS: "1" # concurrent PM (Jira/Linear/etc.) jobs
MAX_SENSORS_JOBS: "5" # concurrent sensors jobs
The defaults shown above match v4 behaviour for most installations. Ops jobs bypass concurrency limits in both versions.
4. Datadog chart API changes
If you have Datadog enabled, remove the following keys — they no longer exist in the upgraded Datadog subchart:
datadog-agent:
datadog:
kubeStateMetricsEnabled: true # remove
containerExcludeLogs: "image:..." # remove
kubeStateMetrics:
image: ... # remove entire block
You must also explicitly disable the Datadog Operator to prevent it from being installed on upgrade:
MinIO update strategy migration (Argo CD with ServerSideApply only)
If you use plain
helm upgrade, skip this section.
See README-upgrade-to-v4.md for background. The same issue applies on the v4 → v5 upgrade path when using Argo CD with ServerSideApply. Enable the migrator once on the upgrading release:
Disable it again after the upgrade completes.
New features
Per-queue job tuning
You can tune the job lifecycle behaviour per environment. All of the following have working defaults and only need to be set if you want to change them:
global:
# How long before a stuck job is forcibly terminated (seconds)
JOB_ACTIVE_DEADLINE_SECONDS: "3600"
# How long completed / failed job pods are kept before cleanup
JOB_TTL_SUCCESS_SECONDS: "60"
JOB_TTL_FAILED_SECONDS: "21600"
# Retry behaviour
MAX_JLM_RETRY_ATTEMPTS: "5"
SCHEDULER_REQUEUE_DELAY: "600"
MAX_SCHEDULER_RETRY_ATTEMPTS: "10"
Argo CD namespace RBAC
If you deploy via Argo CD with the application controller running in a separate namespace, the chart can create the necessary RoleBinding for you:
global:
argocdNamespaceRbac:
enabled: true
controllerServiceAccountName: "argocd-application-controller"
controllerNamespace: "argocd"
clusterRoleName: "admin"
Default is false. Leave disabled if you manage RBAC out-of-band.
OpenShift + Argo CD (openshift-gitops): The GitOps service account typically does not have escalation rights to bind
admin. Keep this disabled and grant RBAC to the GitOps SA via your cluster's infrastructure tooling instead.
Secret management via External Secrets Operator (ESO)
v5 adds support for supplying secrets through the External Secrets Operator. Set global.infra.commonSecret.mode to choose how the chart-secrets Kubernetes Secret is populated:
| Mode | Description |
|---|---|
helm | Default. Helm renders the Secret from your values file. |
externalSecret | ESO syncs the Secret from a ClusterSecretStore. Requires ESO installed in your cluster. |
existing | You pre-create the Secret outside the chart (e.g. Terraform, Vault agent). |
ESO example — sync from AWS Secrets Manager:
global:
infra:
commonSecret:
mode: externalSecret
externalSecrets:
refreshInterval: "1h"
secretStoreRef:
name: my-cluster-secret-store # name of your ClusterSecretStore
kind: ClusterSecretStore
envName: prod
data:
- secretKey: LINEARB_PUBLIC_API_KEY
remoteRef:
property: LINEARB_PUBLIC_API_KEY
- secretKey: DD_API_KEY
remoteRef:
property: DD_API_KEY
- secretKey: api-key
remoteRef:
property: DD_API_KEY
- secretKey: JFROG_KEY
remoteRef:
property: JFROG_KEY
- secretKey: JFROG_USER
remoteRef:
property: JFROG_USER
The image pull secret (regcred) can also be managed by ESO:
Custom root CA — ESO mode
If you supply a custom root certificate and want it sourced from a secret store rather than embedded in your values file:
global:
CUSTOM_ROOT_CERTIFICATES: true
customCa:
secretMode: eso
externalSecret:
refreshInterval: 1h
secretStoreRef:
name: my-cluster-secret-store
kind: ClusterSecretStore
remoteRef:
key: my-secret-id
property: CUSTOM_SSL_CERT
The secret store property must be populated before the release is applied. If the ExternalSecret remains in
Pendingstate, the CA bundle init container will fail to start.
OpenShift: security context requirements
OpenShift's restricted-v2 SCC requires pods to run with UIDs within your namespace's allocated range. v5 retains the same security context requirements as v4.
Find your namespace UID range:
oc get ns <your-namespace> -o jsonpath='{.metadata.annotations.openshift\.io/sa\.scc\.uid-range}{"\n"}'
# example: 1000900000/10000 → use 1000900000 as your base UID
Set the global OpenShift UID
LinearB's own workloads pick up security context automatically when K8S_PLATFORM: openshift is set. Configure the namespace UID once globally:
global:
K8S_PLATFORM: openshift
openshiftRunAsUser: 1000900000 # replace with your namespace UID range low bound
openshiftRunAsGroup: 1000900000
Bitnami subchart overrides (MinIO, RabbitMQ, Redis)
The MinIO, RabbitMQ, and Redis subcharts do not read the global OpenShift UID and must be overridden explicitly. Replace 1000900000 throughout with your namespace's UID range low bound.
minio:
persistence:
subPath: minio-export
securityContext:
enabled: true
runAsNonRoot: true
runAsUser: 1000900000
runAsGroup: 1000900000
fsGroup: 1000900000
fsGroupChangePolicy: "Always"
containerSecurityContext:
runAsNonRoot: true
runAsUser: 1000900000
runAsGroup: 1000900000
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
postJob:
securityContext:
enabled: true
runAsNonRoot: true
runAsUser: 1000900000
runAsGroup: 1000900000
fsGroup: 1000900000
makeBucketJob:
securityContext:
enabled: true
containerSecurityContext:
runAsNonRoot: true
runAsUser: 1000900000
runAsGroup: 1000900000
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
makeUserJob:
securityContext:
enabled: true
containerSecurityContext:
runAsNonRoot: true
runAsUser: 1000900000
runAsGroup: 1000900000
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
rabbitmq:
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000900000
runAsGroup: 1000900000
fsGroup: 1000900000
fsGroupChangePolicy: Always
containerSecurityContext:
runAsNonRoot: true
runAsUser: 1000900000
runAsGroup: 1000900000
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
redis:
master:
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000900000
runAsGroup: 1000900000
fsGroup: 1000900000
fsGroupChangePolicy: Always
containerSecurityContext:
runAsNonRoot: true
runAsUser: 1000900000
runAsGroup: 1000900000
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
fsGroupChangePolicy: Alwayson MinIO and RabbitMQ forces kubelet to sync PVC ownership on every mount. This prevents startup failures ("Unable to write to the backend") if the UID changed between deployments.
minio.persistence.subPath: minio-exportmounts MinIO's data into a subdirectory of the PVC so new data is created under the current UID. Without this, data written before a UID change may be inaccessible after upgrade.
Supplemental group for analysis workloads
The sensors, onprem-receiver, jobs-suite-worker, and jobs-suite-dispatcher containers write to paths that require membership in group 0. Add the following to your OpenShift values layer:
sensors:
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
supplementalGroups: [0]
onprem-receiver:
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
supplementalGroups: [0]
jobs-suite-worker:
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
supplementalGroups: [0]
jobs-suite-dispatcher:
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
supplementalGroups: [0]
agent-api runtime flags
agent-api:
image:
env:
plain:
- name: "PORT"
value: "8080"
- name: "GUNICORN_CMD_ARGS"
value: "--no-control-socket --worker-tmp-dir /tmp"
Forward-proxy DNS resolver
OpenShift uses a different internal DNS service than standard Kubernetes. Set the resolver so the forward proxy can resolve upstream hostnames:
OpenShift Route for webhook receiver
On OpenShift the webhook receiver is exposed via an OpenShift Route rather than an Ingress. Configure it under infra:
infra:
openshift:
onpremReceiverRoute:
annotations:
haproxy.router.openshift.io/timeout: 120s
# host: "" # optional — omit to use the cluster-assigned hostname
Ensure global.RECEIVER_INGRESS: false and ingress.installController: false are set so the bundled ingress-nginx controller is not installed.
Upgrade steps
- Update your
local-values.yamlwith the breaking changes listed above. - Pull the latest chart:
- Run a dry-run to preview changes:
- Apply the upgrade:
- Verify all pods come up:
For issues during or after the upgrade, refer to the Diagnostics Guide.