OpenShift & OKD — Self-Hosted Installation
RunWhen Platform installs on OpenShift and OKD using the same Helm chart as any other Kubernetes distribution. The functional differences at deploy time all trace back to one thing: OpenShift enforces Security Context Constraints (SCCs) at admission. A few chart components — most importantly the bundled Postgres image (Spilo) and Vault’s Kubernetes auth — require actions that the namespace / project owner cannot perform on their own.
This page is written for the cluster administrator (or platform / security engineer) who has to sign off on those actions. The goal is to explain, in plain terms, what is being asked for, why each ask is necessary for the platform to run, and why granting it does not weaken your cluster’s security posture. Once these one-time actions are in place, the application team can install and upgrade RunWhen from inside a single namespace with no cluster-scoped permissions.
If you are the person running helm install, read Self-Hosted Deployment Requirements for the shared prerequisites (registry, DNS, TLS, LLM), then come back here for the OpenShift-specific pieces.
The short version
There are exactly three cluster-admin actions you will be asked to take once, before the namespace owner runs Helm:
| # | Action | Scope | Reversible? |
|---|---|---|---|
| 1 | Create an image pull secret in the install namespace | One namespace, one Secret | Delete the Secret |
| 2 | Grant the anyuid SCC to one ServiceAccount ({release}-postgresql) | One ServiceAccount in one namespace | oc adm policy remove-scc-from-user |
| 3 | Create one ClusterRoleBinding for Vault’s token-review call (system:auth-delegator) | One SA can call TokenReview | kubectl delete clusterrolebinding |
| 4 | (Recommended) Apply ResourceQuota and LimitRange for the install namespace, or bake into ProjectRequestTemplate | One namespace or cluster-wide | Delete the quota / template |
Everything else the chart installs — UI, PAPI, AgentFarm, search, LLM gateway, object store — runs as an arbitrary non-root UID under stock restricted, with read-only root filesystems, dropped capabilities, and the runtime default seccomp profile. The Postgres exception and the Vault binding are the only two places the chart cannot fit inside a stock namespace-scoped install.
If your policy forbids anyuid entirely, you have two alternatives:
- Custom SCC — allow only UID
101(Spilo’s baked-in user), bound only to{release}-postgresql. This is a stricter, allow-listed version of the same exception. - External Postgres — set
postgresql.kind: externaland point the chart at a database you already run. Skips both the SCC exception and the Vault-adjacent chart Postgres entirely.
Details, YAML, and the reasoning behind each choice are below.
Why OpenShift needs a different conversation
Vanilla Kubernetes enforces workload identity through Pod Security Standards (Baseline / Restricted) and whatever admission webhook you layer on top. Chart defaults that pin runAsUser: 1000 satisfy Restricted because the standard only says “non-root” — any non-zero UID is fine.
OpenShift is stricter by design. It assigns each project a UID range — for example 1000810000/10000 — and its default SCC (restricted-v2) rejects any pod that pins a UID outside that range, even a normal non-root value like 1000. The philosophy is straightforward: no two projects should be able to run as the same UID, so a container-escape or shared-volume mistake in one namespace cannot masquerade as another.
That model works well when an image is built to accept whatever UID it is handed. It struggles with images that hard-code a UID at build time — because their entrypoint expects specific /etc/passwd entries, chowns files to a known owner, or writes to paths owned by a fixed group. Those images cannot simply run as 1000817342 and hope for the best.
RunWhen first-party images are built to accept any non-root UID; they will run cleanly under stock restricted. Bundled Spilo is not: it expects UID 101 and fsGroup 103 because that is how the upstream Zalando image is baked. When OpenShift admission hands Spilo a random project UID, its bootstrap chown calls fail (Operation not permitted) and the pod crash-loops before Postgres ever starts.
You have four ways to reconcile that mismatch, and the rest of this page walks through them.
The four options, and when each one is appropriate
| Option | What it does | Stays on stock restricted? | Best fit |
|---|---|---|---|
A — Strict restricted | Chart values null every fixed UID; everything runs as the project’s assigned UID | Yes, for workloads that admit | First choice; everything except Spilo works |
B — Scoped anyuid for Postgres | Restore Spilo’s UID 101, grant anyuid only to {release}-postgresql | No — one ServiceAccount only | Recommended default for bundled Postgres |
C — Custom SCC (UID 101 allow-list) | Cluster-admin defines an SCC that permits only UID 101; bind it to {release}-postgresql | Partial (not stock restricted, but narrower than anyuid) | Enterprises that forbid anyuid outright |
| D — External Postgres | postgresql.kind: external; you run Postgres elsewhere | Yes | You already operate a Postgres you trust — see External PostgreSQL |
Everything that follows assumes Option A for the general fleet plus Option B (or C, or D) for Postgres. Redis, Neo4j, and Vault are optional exceptions only if Option A hits a runtime problem in your cluster — we will get to those at the end.
What the cluster admin actually grants — and why it is safe
The two SCC/RBAC actions you will sign off on are narrower than they sound. This section is deliberately explicit about what changes and what does not.
1. anyuid for the Postgres ServiceAccount
What the ask is. Bind the built-in system:openshift:scc:anyuid ClusterRole to exactly one ServiceAccount — {release}-postgresql — inside the RunWhen namespace. Nothing else in the namespace gains this ability.
What anyuid actually relaxes. Compared to restricted-v2, anyuid differs in one dimension: pods bound to it may pin a specific UID instead of accepting one from the project range. Everything else still applies:
- Pods still run non-root (Spilo runs as UID
101, not0) - Root filesystem is still read-only where the chart sets it
- Linux capabilities are still dropped (
drop: ["ALL"],add: []) - No
hostNetwork,hostPID,hostIPC, orprivilegedcontainers - No hostPath volumes
- No new access to the API server, no new RBAC, no new secrets
The one wrinkle: stock OpenShift anyuid has Allowed Seccomp Profiles: <none> — it will refuse the pod entirely if seccompProfile: RuntimeDefault is set. The overlay clears that field on the Spilo pod, which sounds worse than it is: with capabilities dropped, no hostPath, no privilege escalation, and a fixed unprivileged UID, the seccomp filter was already the weakest defense in the stack for this specific pod. If your policy requires seccomp, use Option C (custom SCC) and add Allowed Seccomp Profiles: runtime/default — otherwise this is a normal accepted trade-off in OpenShift Postgres installs.
Why this is a standard OpenShift pattern. Red Hat documents oc adm policy add-scc-to-user anyuid -z <sa> as the recommended way to run community images that expect a specific UID — the same fix is used for Bitnami charts, MinIO, JupyterHub, upstream Grafana, and most vendor-provided Postgres/Redis operators. It is not a workaround; it is the sanctioned path for images that predate OpenShift’s random-UID model.
Blast radius. A single ServiceAccount in a single namespace. Any pod that does not use that ServiceAccount — including everything else RunWhen installs — still admits through restricted unchanged. You can audit the exception with:
oc get rolebindings,clusterrolebindings --all-namespaces \ -o jsonpath='{range .items[?(@.roleRef.name=="system:openshift:scc:anyuid")]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}'Apply it. Use oc adm policy for a quick grant, or apply the RoleBinding YAML so the exception lives in git alongside your other cluster configuration:
export NS=runwhen-platformexport RELEASE=rw
oc adm policy add-scc-to-user anyuid \ -z "${RELEASE}-postgresql" -n "$NS"Show the equivalent RoleBinding YAML
The chart ships this manifest at charts/runwhen-platform/manifests/openshift/rw-postgresql-anyuid.rolebinding.yaml so the exception is reviewable in a pull request instead of living only in shell history.
apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: system:openshift:scc:anyuid namespace: runwhen-platform labels: app.kubernetes.io/part-of: runwhen-platform runwhen.com/scc-exception: spilo annotations: runwhen.com/reason: >- Spilo requires image UID 101; OpenShift restricted-v2 assigns a random project UID and Spilo bootstrap chown/crontab fails. Scoped to the Postgres ServiceAccount only — not a namespace-wide anyuid grant.roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:openshift:scc:anyuidsubjects: - kind: ServiceAccount name: rw-postgresql namespace: runwhen-platformAdjust metadata.namespace, the RoleBinding subject namespace, and the SA name ({release}-postgresql) to match your release.
Verify. After the binding exists, confirm the Postgres SA can actually use anyuid before the namespace owner runs Helm:
oc auth can-i use scc/anyuid \ --as=system:serviceaccount:${NS}:${RELEASE}-postgresql# expect: yesPrefer a custom SCC (Option C)? Define an SCC that lists only UID 101, still drops all capabilities, still forbids host access, and bind it the same way. This is stricter than anyuid because it hard-caps the allowed UID rather than accepting any. If your platform team already maintains custom SCCs for third-party workloads, this is likely how you will handle Postgres.
2. ClusterRoleBinding for Vault’s Kubernetes auth
What the ask is. Create one ClusterRoleBinding that lets Vault’s ServiceAccount call the Kubernetes TokenReview API. Vault uses that call to validate ServiceAccount tokens presented by other pods when they authenticate — nothing else.
kubectl create clusterrolebinding "${RELEASE}-vault-server-binding" \ --clusterrole=system:auth-delegator \ --serviceaccount="${NS}:${RELEASE}-vault"Why it is cluster-scoped. TokenReview is a cluster resource — you can only grant access to it with a ClusterRoleBinding. That is why the Helm chart cannot ship this on behalf of a namespace-scoped admin: only cluster admins can create the binding. This is the same pattern used by every product that validates ServiceAccount tokens (external OIDC integrations, workload-identity plugins, admission webhooks).
What system:auth-delegator allows. Exactly two things:
createontokenreviews.authentication.k8s.iocreateonsubjectaccessreviews.authorization.k8s.io
It grants no read or write access to workloads, Secrets, Namespaces, RBAC, or anything else in the cluster. It is Kubernetes’ least-privilege binding for “this service needs to answer ‘is this token valid?‘“.
3. Namespace pull secret
What the ask is. Create a kubernetes.io/dockerconfigjson Secret in the install namespace so the cluster can pull RunWhen’s images from the private Google Artifact Registry (or your enterprise mirror).
Why not rely on the cluster’s global pull secret. OpenShift nodes do not automatically inherit GAR credentials the way GKE does (via Workload Identity). Even when the cluster is inside the same GCP project that hosts the registry, node service accounts do not have roles/artifactregistry.reader on the RunWhen repository. Creating a namespace-scoped pull secret keeps the credential blast-radius small and lets you rotate it without touching cluster-wide config.
The namespace owner can create this secret themselves if they have secrets/create in the namespace; only involve the cluster admin if your policy centralizes registry credentials. The default secret name the chart looks for is gcr-pull-secret (change via images.pullSecrets in values).
The two Helm value overlays
Your onboarding pack ships two reference values files that pair with the SCC decisions above. The namespace owner layers them on top of their environment-specific overlay (domain, TLS, LLM, etc.):
helm upgrade --install rw ./runwhen-platform \ -n runwhen-platform --create-namespace \ -f values-example-openshift-restricted.yaml \ -f values-example-openshift-scc-exceptions.yaml \ -f your-domain-and-registry.yamlThe order matters: restricted first (sets the baseline of “run as whatever UID OpenShift gives you”), scc-exceptions second (overrides only the components that need a fixed UID).
Option A overlay — values-example-openshift-restricted.yaml
This file’s job is one thing: null every UID / group the chart pins by default, so every workload accepts the UID OpenShift assigns from the project range. Helm deep-merges values, which means omitting runAsUser does not remove it — you must explicitly set it to null for the chart to drop the key.
Show the full Option A overlay
# OpenShift / OKD — Option A: stay on restricted (no anyuid)## Helm deep-merges values: set runAsUser/fsGroup to null to clear chart defaults.# Do NOT set hostUsers — some OKD apiservers reject undeclared fields.## Spilo is intentionally LEFT on its chart default UID 101 in this file so that# admission FAILS loud if you have not applied Option B/C/D. To attempt a# doomed arbitrary-UID Spilo experiment, null those keys yourself and expect# bootstrapping chown/crontab failure.
global: podSecurityContext: runAsNonRoot: true runAsUser: null runAsGroup: null fsGroup: null seccompProfile: type: RuntimeDefault containerSecurityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: null capabilities: drop: ["ALL"]
redis: master: podSecurityContext: enabled: true runAsNonRoot: true runAsUser: null fsGroup: null seccompProfile: type: RuntimeDefault containerSecurityContext: enabled: true runAsNonRoot: true runAsUser: null allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"]
neo4j: securityContext: runAsNonRoot: true runAsUser: null fsGroup: null seccompProfile: type: RuntimeDefault containerSecurityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: null capabilities: drop: ["ALL"]
qdrant: podSecurityContext: runAsNonRoot: true runAsUser: null fsGroup: null seccompProfile: type: RuntimeDefault containerSecurityContext: runAsNonRoot: true runAsUser: null allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] updateVolumeFsOwnership: false
vault: server: statefulSet: securityContext: pod: runAsNonRoot: true runAsUser: null fsGroup: null seccompProfile: type: RuntimeDefault container: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: null capabilities: add: [] # clear chart-default IPC_LOCK (forbidden on restricted) drop: ["ALL"]
seaweedfs: master: podSecurityContext: enabled: true runAsNonRoot: true runAsUser: null fsGroup: null seccompProfile: type: RuntimeDefault logs: type: emptyDir volume: podSecurityContext: enabled: true runAsNonRoot: true runAsUser: null fsGroup: null seccompProfile: type: RuntimeDefault logs: type: emptyDir filer: podSecurityContext: enabled: true runAsNonRoot: true runAsUser: null fsGroup: null seccompProfile: type: RuntimeDefault data: type: persistentVolumeClaim size: "5Gi" storageClass: "" logs: type: emptyDir s3: podSecurityContext: enabled: true runAsNonRoot: true runAsUser: null fsGroup: null seccompProfile: type: RuntimeDefault
llmGateway: podSecurityContext: runAsNonRoot: true runAsUser: null fsGroup: null seccompProfile: type: RuntimeDefaultA few points worth highlighting for reviewers:
runAsNonRoot: trueis preserved everywhere. NullingrunAsUserallows OpenShift to pick one — it does not allow the pod to run as root.readOnlyRootFilesystem: trueandcapabilities.drop: ["ALL"]stay in place across every component. Nothing here relaxes the container’s ability to write to its own filesystem or use privileged syscalls.- Vault has
capabilities.add: []— the chart normally addsIPC_LOCKso Vault canmlock()its memory to prevent unseal keys from swapping to disk.restricteddoes not allowIPC_LOCK, so on OpenShift Vault starts without mlock and prints a warning. This is a documented, accepted trade-off for OpenShift installs; production installs that require mlock can either use a custom SCC that permitsIPC_LOCKfor the Vault SA only, or switch to an external Vault.
Option B overlay — values-example-openshift-scc-exceptions.yaml
This file’s job is narrower: restore the fixed UID for Spilo so the image can actually start, and clear its seccomp profile so stock anyuid will match. Redis, Neo4j, and Vault exceptions are shipped commented out — enable them only if Option A hits an EACCES / capability failure in your specific cluster.
Show the full Option B overlay
# OpenShift / OKD — Option B: documented SCC exceptions for fixed-UID images## Apply manifests/openshift/*.rolebinding.yaml as cluster-admin BEFORE or with# this overlay. Do not grant namespace-wide anyuid.## Layer ON TOP of values-example-openshift-restricted.yaml:# helm upgrade ... \# -f values-example-openshift-restricted.yaml \# -f values-example-openshift-scc-exceptions.yaml## Today the only REQUIRED exception for a working Postgres is Spilo.# Redis / Neo4j / Vault rows are OPTIONAL — enable when Option A hits EACCES# or an IPC_LOCK admission failure; keep commented until then.
# --- REQUIRED for bundled Spilo (does NOT work under restricted alone) -------# Stock OpenShift `anyuid` forbids seccompProfiles — clear seccompProfile or# anyuid will not match on admission.postgresql: spilo: podSecurityContext: runAsNonRoot: true runAsUser: 101 fsGroup: 103 seccompProfile: null containerSecurityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 101 capabilities: drop: ["ALL"]
# Cluster-admin (once per namespace):# oc apply -f charts/runwhen-platform/manifests/openshift/rw-postgresql-anyuid.rolebinding.yaml## If pgdata PVC was created under a random OpenShift fsGroup, delete it before# the first successful run as UID 101:# kubectl -n "$NS" delete pvc pgdata-<release>-postgresql-0
# --- OPTIONAL: uncomment if Redis crashes with EACCES under Option A ---------# redis:# master:# podSecurityContext:# enabled: true# runAsNonRoot: true# runAsUser: 1001# fsGroup: 1001# seccompProfile:# type: RuntimeDefault# containerSecurityContext:# enabled: true# runAsNonRoot: true# runAsUser: 1001# allowPrivilegeEscalation: false# readOnlyRootFilesystem: true# capabilities:# drop: ["ALL"]# Then: oc adm policy add-scc-to-user anyuid -z <release>-redis-master -n "$NS"
# --- OPTIONAL: Neo4j UID 7474 ------------------------------------------------# neo4j:# securityContext:# runAsNonRoot: true# runAsUser: 7474# fsGroup: 7474# seccompProfile:# type: RuntimeDefault
# --- OPTIONAL: Vault with mlock (IPC_LOCK) -----------------------------------# Requires an SCC that allows IPC_LOCK (not stock restricted). anyuid is one# blunt tool; prefer a custom SCC in production.# vault:# server:# statefulSet:# securityContext:# pod:# runAsNonRoot: true# runAsUser: 100# fsGroup: 1000# seccompProfile:# type: RuntimeDefault# container:# allowPrivilegeEscalation: false# readOnlyRootFilesystem: true# capabilities:# add: ["IPC_LOCK"]# drop: ["ALL"]Reviewer-friendly notes:
- Spilo still runs as an unprivileged UID.
101is Zalando’s baked-in Postgres user, not root. Read-only root FS and dropped capabilities are still enforced. - The optional sections stay commented on purpose. Turning them on requires a matching cluster-admin binding; if a section is uncommented in a customer’s overlay, there should be a paired RoleBinding checked into the same change.
Component behaviour at a glance
| Component | On stock restricted (Option A) | If it doesn’t work |
|---|---|---|
| RunWhen first-party apps (PAPI, UI, AgentFarm, USearch, LLM gateway, …) | Runs with any assigned UID | — |
| SeaweedFS, Qdrant | Admits; validate in your cluster | Externalize object store / vector DB |
| Postgres (Spilo) | Does not work | Option B (anyuid on the Postgres SA), Option C (custom SCC allowing UID 101), or Option D (external Postgres) |
| Vault (bundled) | Admits without IPC_LOCK; mlock disabled with a warning | Custom SCC allowing IPC_LOCK for Vault SA only, or external Vault |
| Redis (Bitnami) | Usually admits; may hit EACCES on data files depending on image build | Uncomment Redis section of Option B and add matching RoleBinding |
| Neo4j | Usually admits; may hit EACCES on /data | Uncomment Neo4j section of Option B and add matching RoleBinding |
Install sequence
- Cluster admin — Create the pull secret (or delegate), apply the Postgres SCC RoleBinding, apply the Vault
system:auth-delegatorClusterRoleBinding. Verify withoc auth can-i. - Cluster admin (recommended) — Configure a ResourceQuota and LimitRange for the install namespace, or bake them into the
ProjectRequestTemplateso every new project gets them automatically (see Resource quotas below). - Namespace owner — Register Helm subchart repos, pull the OCI chart, prepare domain / registry / LLM overlays.
- Namespace owner —
helm upgrade --installwith the restricted overlay + scc-exceptions overlay + your environment overlay. - Both — Watch pod events. If a workload fails admission, the pod event will name the SCC it evaluated against — that is your diagnostic.
- Verify — Run the
install-checklist.sh verifyscript from the onboarding pack. - Runners — Deploy RunWhen Local pointed at the platform URL. Runner SCC needs are typically lighter than the platform stack.
Full self-hosted install steps that apply to every distribution (registry, DNS, TLS, LLM) live in Self-Hosted Deployment Requirements.
Resource quotas
The platform uses emptyDir scratch volumes for caches and working directories that can consume node disk space. The chart sets sizeLimit on every emptyDir, but this is an eviction signal, not a hard quota (see the main guide’s storage section for details).
Apply this to the install namespace before running Helm:
apiVersion: v1kind: ResourceQuotametadata: name: runwhen-platform-quotaspec: hard: requests.ephemeral-storage: "50Gi" limits.ephemeral-storage: "100Gi" requests.storage: "200Gi" persistentvolumeclaims: 20---apiVersion: v1kind: LimitRangemetadata: name: runwhen-platform-limitsspec: limits: - type: Container default: ephemeral-storage: "2Gi" defaultRequest: ephemeral-storage: "256Mi" max: ephemeral-storage: "10Gi"Recommended for OpenShift: Bake these into the ProjectRequestTemplate so every new project is created with quotas from day zero:
apiVersion: template.openshift.io/v1kind: Templatemetadata: name: project-requestobjects: - apiVersion: project.openshift.io/v1 kind: Project metadata: name: ${PROJECT_NAME} - apiVersion: v1 kind: ResourceQuota metadata: name: platform-quota namespace: ${PROJECT_NAME} spec: hard: requests.ephemeral-storage: "50Gi" limits.ephemeral-storage: "100Gi" - apiVersion: v1 kind: LimitRange metadata: name: platform-limits namespace: ${PROJECT_NAME} spec: limits: - type: Container default: ephemeral-storage: "2Gi" defaultRequest: ephemeral-storage: "256Mi" max: ephemeral-storage: "10Gi"Verify the template is active:
oc get project.config.openshift.io/cluster -o jsonpath='{.spec.projectRequestTemplate.name}'If unset, apply the template and configure the cluster to use it:
oc apply -f project-request-template.yamloc patch project.config.openshift.io/cluster --type=merge \ -p '{"spec":{"projectRequestTemplate":{"name":"project-request"}}}'Troubleshooting
unable to validate against any security context constraint
The pod’s requested UID / capabilities / seccomp profile does not match any SCC the pod’s ServiceAccount can use. The pod event will list which SCCs were evaluated and why each rejected the pod — read those messages first.
- If it names
restricted-v2and complains about UID or fsGroup, the workload still has a pinned UID that the Option A overlay should have nulled — check for a values overlay that re-pinsrunAsUser. - If it names
anyuidand complains about seccomp, the pod still hasseccompProfile: RuntimeDefault. The Option B overlay clears this on Spilo; make sure your overlay merge order is-f restricted.yaml -f scc-exceptions.yaml(exceptions second).
Spilo CrashLoopBackOff with chown: Operation not permitted or crontab errors
The Postgres exception is not in effect for this pod. Either the RoleBinding is missing / points at the wrong SA, or the pod is not using {release}-postgresql. Confirm with:
oc get pod <spilo-pod> -o jsonpath='{.spec.serviceAccountName}{"\n"}'oc auth can-i use scc/anyuid \ --as=system:serviceaccount:${NS}:$(oc get pod <spilo-pod> -o jsonpath='{.spec.serviceAccountName}')If the PVC was created under a random OpenShift fsGroup before the exception was applied, delete it once so it can be recreated with fsGroup 103.
ImagePullBackOff despite correct values
The namespace pull secret is missing, misnamed, or the nodes cannot reach the registry. Confirm the secret exists in the install namespace, that images.pullSecrets in your values names it, and that pods can resolve and reach the registry host.
Vault log line: Failed to lock memory: cannot allocate memory
Expected on stock restricted — the chart cleared IPC_LOCK because SCC forbids it. Vault runs correctly but without mlock. If your policy requires mlock, use a custom SCC that permits IPC_LOCK for the Vault SA only, or run Vault externally.
Related documentation
| Topic | Link |
|---|---|
| Self-hosted prerequisites (all distributions) | Self-Hosted Requirements |
| Pod security defaults | Self-Hosted Requirements — Security |
| Enterprise registry (JFrog) | JFrog Registry Setup |
| Enterprise registry (GAR) | GAR Registry Setup |
| Private runners | SaaS with Private Runner |
Get help
Email support@runwhen.com with your OpenShift/OKD version, your SCC posture (stock, custom SCC, or anyuid policy), and whether you plan to use bundled or external Postgres/Vault. Attaching oc describe pod output for any failing workload gets you a targeted answer faster than a general policy question.