Tenant onboarding — overview

End-to-end checklist a platform operator follows to onboard a new (division, team, app) — Vault role, AppProject, ESO Secret, pull secret, AppSet entry, RHACS exception flow.

This page is the single checklist a platform operator follows to take a new (division, team, app) from “nothing” to “Argo syncs a working Deployment that pulls images, reads Vault secrets, and serves a Route.” It is concrete enough that a new operator can follow it end to end without back-channel guidance.

The five “platform-side” pages below each expand one step in detail.

Whiteboard — onboarding step order

The numbering matters. Steps 1-4 are platform setup that must complete before any tenant manifest is reconciled. Step 5 is the per-tenant ESO bridge. Step 6 is the cluster-wide pull secret materialisation. Step 7 is one-time per division (skipped for new apps in an existing monorepo). Steps 8-10 are the tenant’s actual app delivery.

Checklist

The full sequence for a new division + new team + new app is:

1. Pick a division name

Lowercase, no underscores. Examples: platform, payments, risk, retail.

This becomes part of:

  • the namespace prefix (apps-<division>-*)
  • the Vault role (apps-<cluster>-<division>)
  • the Vault policy (apps-<division>-read)
  • the Vault path subtree (secret/apps/<division>/...)

Add the entry to platform-gitops/divisions.yaml (one-line addition).

2. Run the Vault onboarding script

/home/ze/ops-workspace/scripts/vault-apps-onboard.sh <division> <cluster>
# e.g.
/home/ze/ops-workspace/scripts/vault-apps-onboard.sh platform spoke-dc-v6

This creates (or rewrites — idempotent):

  • Vault ACL policy apps-<division>-read
  • Vault role apps-<cluster>-<division> under auth/kubernetes-<cluster>/

Full details: 02 — Vault path and bound role.

3. Create the AppProject + team RoleBinding

One MR to platform-gitops:

  • clusters/<cluster>/appprojects/team-<team>.yaml — Argo AppProject scoped to the team’s app monorepo and apps-<division>-* destinations.
  • clusters/<cluster>/tenants/_template/rolebinding-edit.yaml — copied into clusters/<cluster>/tenants/apps-<division>-<team>/rolebinding-team-developers.yaml with placeholders substituted.

Full details: 03 — Project + RoleBinding template.

4. Create the tenant namespace

Same MR as step 3, adds:

  • clusters/<cluster>/tenants/apps-<division>-<team>-<env>/namespace.yaml (one per env: dev, stg, prd).
  • Labels: apps.platform/division=<division>, apps.platform/team=<team>, apps.platform/env=<env>, apps.platform/tenant=true (this last label is what triggers the cluster-wide pull-secret fan-out).
  • PSA labels: pod-security.kubernetes.io/enforce=restricted (PCI profile, ADR 0020).
  • The argocd.argoproj.io/managed-by: openshift-gitops annotation so Argo recognises the namespace.

5. Create the ResourceQuota + LimitRange

Same MR. See 06 — ResourceQuota and LimitRange for the canonical shapes.

Default budgets:

  • dev: 4 CPU / 8 Gi memory / 10 pods / 0 PVCs
  • stg: 8 CPU / 16 Gi memory / 20 pods / 5 PVCs
  • prd: 16 CPU / 32 Gi memory / 40 pods / 10 PVCs

Tenants can request increases via a GitLab MR to the namespace’s quota YAML, reviewed by the platform team.

6. Materialise the per-tenant SecretStore

In the tenant’s app monorepo, create apps/<team>/<app>/base/secretstore-vault-apps.yaml from the template at platform-gitops/clusters/<cluster>/tenants/_template/secretstore-vault-apps.yaml, substituting <DIVISION>, <APP>, and <CLUSTER> placeholders.

This is the only namespaced SecretStore for the tenant — not a ClusterSecretStore. It points the namespace’s app-eso ServiceAccount at the Vault role created in step 2.

Full details: 04 — ESO Secret and pullSecret.

7. Verify the cluster-wide app-registry pull Secret

The ClusterExternalSecret/app-registry-pull (shipped from platform-gitops/clusters/<cluster>/secrets/app-registry-pull/) watches namespaces labelled apps.platform/tenant=true and materialises a Secret/app-registry-pull of type kubernetes.io/dockerconfigjson inside each.

Confirm:

oc get clusterexternalsecret app-registry-pull -o jsonpath='{.status}'
oc -n apps-<division>-<team>-<env> get secret app-registry-pull

Attach it to the default ServiceAccount:

oc -n apps-<division>-<team>-<env> patch sa default \
  -p '{"imagePullSecrets":[{"name":"app-registry-pull"}]}'

For workloads using a non-default SA, attach to that SA instead.

8. Add an AppSet entry (only if a new app monorepo)

If this division’s apps live in an app monorepo that the tenant-apps ApplicationSet is not already watching, one platform MR adds the new repoURL to the AppSet’s git generator. See 02 — ApplicationSet generator pattern.

If the new app is in an already-watched monorepo, this step is skipped — the AppSet auto-discovers the new overlay directory on its next refresh (default 3 min).

9. RHACS policy scope check

The five built-in deploy-time RHACS policies (Latest tag, No CPU request or memory limit specified, Privileged Container, CAP_SYS_ADMIN capability added, Required Image Label) auto-scope to apps-.*, so a new tenant namespace is covered without action. Verify scope is correct:

ROX_PW=$(oc -n stackrox get secret central-htpasswd -o jsonpath='{.data.password}' | base64 -d)
curl -fsSk -u "admin:$ROX_PW" \
  "https://central-stackrox.apps.hub-dc-v6.sub.comptech-lab.com/v1/policies" \
  | jq '.policies[] | select(.name == "Latest tag") | .scope'

If the tenant needs an exception, follow 05 — RHACS tenant exception process. The default is no exception — fix the violation in the manifest first.

10. Tenant onboards the app

Tenant opens an MR against their app monorepo that adds apps/<team>/<newapp>/{base,overlays/{dev,stg,prd}}/ following the overlay contract.

11. Smoke validation

Once tenant merges:

# AppSet should have created Applications for dev/stg/prd.
oc -n openshift-gitops get applications | grep '<team>-<newapp>'

# Argo should be Synced/Healthy on dev (no CI digest yet, so on initial onboard the
# placeholder digest causes ImagePullBackOff — that's expected. CI's first build patches it.)
oc -n openshift-gitops get app team-<team>-<newapp>-dev -o jsonpath='{.status.sync.status}{" "}{.status.health.status}{"\n"}'

# Tenant runs a manual build through Path A (Jenkins) or Path B (Tekton). After CI's
# first successful build merges the digest patch MR, Argo re-syncs and the Pod runs.

# Final smoke:
oc -n apps-<division>-<team>-dev get pods
oc -n apps-<division>-<team>-dev get route <newapp> -o jsonpath='{.status.ingress[0].host}{"\n"}'
curl -fsS https://$ROUTE_HOST/health

What the sub-pages cover

PageStepPurpose
02 — Vault path and bound role1, 2The per-division Vault role, policy, and path-tree convention.
03 — Project + RoleBinding template3The Argo AppProject + the team’s RoleBinding to the namespace.
04 — ESO Secret and pullSecret5, 6, 7The per-tenant SecretStore + the cluster-wide app-registry pull Secret.
05 — RHACS tenant exception process9The exception-MR pattern when a tenant legitimately needs to bypass a RHACS policy.
06 — ResourceQuota and LimitRange4The default per-env quota / limit shapes and how to request increases.
07 — Tenant scaffolding template3, 4Canonical contents of _template/ plus the four active tenants and the AppProject convention.
08 — Cluster pull-secret fan-out6The dedicated ClusterExternalSecret design — fan-out, Vault path, label selector.
09 — Per-tenant Quay robot token(CI)The Path B push credential: per-tenant Vault path, ESO into openshift-pipelines, shared Tekton Task.

Pre-onboarding requirements

Before step 1, these MUST be in place (they are platform install-time work, not per-tenant):

  • The vault-apps-onboard.sh script is on the operator’s machine with reachable Vault.
  • The platform ClusterExternalSecret/app-registry-pull is shipped on the spoke and its Vault dependency (secret/ocp/<cluster>/registries/app-registry-pull) is populated. See 04 — ESO Secret and pullSecret.
  • The tenant-apps ApplicationSet and its GitOpsCluster Placement wiring are on the hub. See 02 — ApplicationSet generator pattern.
  • The five RHACS built-in policies are scoped to apps-.* and set to SCALE_TO_ZERO_ENFORCEMENT on the DEPLOY lifecycle. Run scripts/rhacs-enable-app-policies.sh once.

What is NOT in scope of “onboarding”

A few things commonly conflated with tenant onboarding but governed elsewhere:

  • The build pipeline itself. Jenkins job (Path A) or Tekton Pipeline (Path B) is the tenant’s responsibility; the platform provides templates only. See 03 — Build paths.
  • The cluster’s identity provider integration. Tenant identity (the GitLab group the RoleBinding references) is platform-install work; per-tenant entries are just RoleBindings to existing groups.
  • App configuration. Anything inside the tenant’s base/ and overlays/ is the tenant’s content. Platform onboarding only creates the frame — namespaces, quotas, ESO bridge, AppProject — that the tenant fills.

References

  • opp-full-plat/connection-details/vault-app-secrets.md (issue #174, DEV-OCP-0.4)
  • opp-full-plat/connection-details/app-registry-pullsecret.md (issue #172, DEV-OCP-0.2)
  • opp-full-plat/connection-details/rhacs-app-policy.md (issue #198, DEV-OCP-4.3)
  • opp-full-plat/connection-details/vap-tenant-exclusions.md (issue #199, DEV-OCP-4.4)
  • Issue #173 (DEV-OCP-0.3) — tenant Project + Role + RoleBinding template.

Last reviewed: 2026-05-11