Skip to content

Multi-Account Renovate CE Rollout — Design

Bead: hl-rn4i Date: 2026-06-15 Status: Reviewed — READY (design-reviewer, round 2)

The self-hosted Mend Renovate Community Edition (CE) that manages the fzymgc-house org (epic hl-2ctz, closed) is being expanded to also manage the seanb4t personal account and the holomush org. This is a config-only change: the same single CE pod, the same public GitHub App (fzymgc-renovate, App ID 4059017), and the same renovate-app ExternalSecret serve all three accounts. No new deployment, no new secrets, no Vault policy change.

Two edits carry the expansion:

  1. Widen mendRnvAutoDiscoverFilter from fzymgc-house/* to all three accounts.
  2. Populate the currently-empty config.js (renovate.config → ConfigMap renovate-ce-config-js) with a house baseline plus the global fork guard.

Per-account membership is then controlled by GitHub App install scope, and forks are excluded deterministically by forkProcessing: "disabled".

Grounded against main at commit #1316 (2026-06-15):

  • Credential model is account-agnostic. CE authenticates as the GitHub App (App-ID + private-key JWT), not per-account tokens. The same renovate-app secret (Vault secret/data/fzymgc-house/cluster/renovate) authorizes every account where the App is installed. ADR hl-543e deliberately chose a public, any-account App for exactly this reason.
  • config.js is an empty stub. Onboarding currently rides stock Renovate defaults (onboardingConfig = config:recommended). There is no ConfigMap file in argocd/app-configs/renovate/; the chart renders the ConfigMap from the renovate.config Helm value, which is unset.
  • Autodiscover scope is mendRnvAutoDiscoverFilter: "fzymgc-house/*" in the Helm valuesObject (argocd/cluster-app/templates/renovate.yaml).
  • In-repo wiring (Source B, argocd/app-configs/renovate/) is just the ExternalSecret + NetworkPolicy.

Account inventory (grounded via gh, 2026-06-15)

Section titled “Account inventory (grounded via gh, 2026-06-15)”
AccountSource reposForksExisting Renovate config
fzymgc-houseorg-wide (live)mature .github/renovate.json
seanb4t49775 of 49
holomush1 (Go)0unknown

The 77 seanb4t forks are the principal hazard: any broad autodiscovery would attempt to onboard them. No preset/.github repo exists in any account.

These were settled during brainstorming (hl-rn4i):

#DecisionChoice
D1Governance modelHybrid: autodiscoverFilter as outer fence (all 3 accounts) + App install scope as inner gate
D2Policy planeInline config.js baseline — no separate preset repo
D3Per-account policySame full baseline for all 3 accounts (no per-account packageRules)
D4Fork handlingforkProcessing: "disabled" pinned globally (overrides the “Selected install → enabled” default; see the override caveat below — a repo’s own renovate.json can still opt back in)
D5OnboardingOnboarding PRs ON — a repo is not managed until its onboarding PR is merged
  • Renovate forkProcessing (deepwiki renovatebot/renovate): autodiscover skips forks by default, but the Mend-App “Selected repositories” install scope flips the default to enabled for selected repos. An explicit forkProcessing: "disabled" in global config overrides that default — so forks stay excluded even under Selected install, and even if a fork is accidentally selected. Caveat (not absolute): repository config sits above global config in Renovate’s hierarchy, so a fork whose own renovate.json sets "forkProcessing": "enabled" will override the global disabled and be processed. The guard covers the default case and every fork that does not explicitly opt back in — which is all 77 current seanb4t forks — but it is a strong default, not an inviolable lock. See the Risks table.
  • autodiscoverFilter (deepwiki + context7 /renovatebot/renovate): accepts an array of minimatch globs / RE2 regex. In env-var form (the chart’s mendRnvAutoDiscoverFilter), commas act as filter delimiters, so "fzymgc-house/*,seanb4t/*,holomush/*" yields three independent filters.
  • Mend chart config plane (helm show values + template, chart 14.6.2): the chart key is renovate.config (a stringified JS file). It renders to a ConfigMap (data key config.js) mounted at /usr/src/app/config.js via subPath; with fullnameOverride: renovate-ce the ConfigMap name is renovate-ce-config-js. The chart stamps a checksum/config.jsFileConfigMap Deployment annotation, so a config change rolls the pod.
  • Config precedence (context7): three tiers — global (config.js) → inherited (org-level) → repository. Global non-global options flow down as defaults; repository config wins on conflict. config.js is the right home for global defaults, but “global” does not mean “unoverridable”: any option a repository may legally set (including forkProcessing) can be overridden in that repo’s own renovate.json. Truly operator-only options are the global-only set (e.g. autodiscover*, onboarding), which repos cannot touch.
AccountApp install scopeRationale
fzymgc-houseAll repos (org-wide, already live)maintained infra org
holomushAll repos (1 repo)trivial; org-wide is fine
seanb4tSelected repositories (repo-by-repo)curated membership

Three layers decide whether a repo is managed, and they compose:

  1. Outer fencemendRnvAutoDiscoverFilter: "fzymgc-house/*,seanb4t/*,holomush/*". Nothing outside the three accounts is ever discovered.
  2. Inner gate — App install scope. For seanb4t, only explicitly-installed repos are visible to CE.
  3. Onboarding PR (D5) — even a discovered, installed repo is not actually managed until its onboarding PR is merged.

The fork guard (D4) sits across all layers: forkProcessing: "disabled" means a fork is skipped regardless of discovery or install scope, and regardless of whether it carries a renovate.json that does not explicitly re-enable processing. The one bypass is a fork whose own renovate.json sets "forkProcessing": "enabled" (repository config outranks global) — see the caveat in the grounding notes and the Risks table. None of the 77 current seanb4t forks carry such an override, and Selected install means a fork must also be deliberately selected, so practical exposure is near-zero.

config.js is the keystone. It ships through the chart’s renovate.config Helm value (multi-line JS string) → renovate-ce-config-js ConfigMap → mounted at /usr/src/app/config.js. Every change is a cluster-repo PR + ArgoCD sync, so policy changes are auditable and revertible.

It carries two kinds of setting:

  • Global controls: the operator-only set (onboarding: true, autodiscover behavior — repos cannot override these) plus global defaults that repos may legally override (forkProcessing: "disabled", dependencyDashboard: true, platform). (autodiscoverFilter itself stays in the chart’s mendRnvAutoDiscoverFilter env knob — its existing, first-class home — to avoid duplicating the scope in two places.)
  • Shared baseline (flows down to every discovered repo as defaults): config:recommended, component-family grouping, automerge tiers, the manager set (terraform, kubernetes, argocd, helm-values, kustomize, ansible-galaxy, pip/pep621), and the _VERSION regex managers — mirroring the existing .github/renovate.json.

D3 means no per-account packageRules: all three accounts get the identical baseline. seanb4t safety comes from curated install scope (D1), not from a weaker policy.

With the inline-config.js choice (D2 over a preset repo), the baseline is authored in config.js and the selfhosted-cluster repo retains its own .github/renovate.json. Repository config overrides global, so there is no functional conflict — but the baseline exists in two places and can drift. This is the conscious tradeoff made in choosing inline config over a shared github>fzymgc-house/renovate-config preset. The implementation plan should keep the two as aligned as practical (e.g. the cluster repo’s renovate.json holding only genuinely repo-specific overrides on top of the global baseline).

Unchanged: single replica, SQLite on a 1Gi Longhorn PVC, 2Gi memory limit. Realistic incremental load is small — holomush (1 repo) plus a curated handful of seanb4t repos on top of the live fzymgc-house load. This is well within headroom. SQLite holds cache-only, rebuildable data, so the failure mode is benign (rebuild on restart). Watch-items for operations:

  • PVC usage trend after seanb4t repos are added.
  • Autodiscover cadence / job-queue depth.

Scaling lever if ever needed: increase PVC size and memory limit — not a re-architecture. Out of scope to change any of these now.

FileActionChange
argocd/cluster-app/templates/renovate.yamlModifyWiden mendRnvAutoDiscoverFilter to "fzymgc-house/*,seanb4t/*,holomush/*"; add renovate.config block (the config.js baseline) to the Helm valuesObject.

No new files: config.js ships inline through the chart’s renovate.config value, so there is no standalone manifest in argocd/app-configs/renovate/. No ExternalSecret, NetworkPolicy, or Vault change. The remaining rollout work is GitHub App installs (out-of-band, per account), not repo files.

  1. Land config (this cluster repo, one PR): add config.js baseline + fork guard via renovate.config; widen mendRnvAutoDiscoverFilter to the three accounts. ArgoCD syncs. Verify fzymgc-house behavior is unchanged (the baseline should match current effective config).
  2. holomush: install the App org-wide (1 repo). Verify an onboarding PR appears and is correct.
  3. seanb4t: install the App on selected repos incrementally. After each batch, verify (a) only intended repos are discovered and (b) no fork ever appears in CE’s repo list or receives a PR.
RiskMitigation
Fork onboarding flood (77 seanb4t forks)forkProcessing: "disabled" (D4) + Selected install (D1) + onboarding PRs (D5)
A fork with its own renovate.json: {forkProcessing: "enabled"} bypasses the global guard (repo config outranks global)Bounded: requires the fork to both be deliberately selected and carry the override; none of the 77 current forks do. Verify per rollout step 3(b). If ever needed, exclude via autodiscoverFilter regex or simply don’t select it.
config.js baseline diverges from cluster repo’s renovate.jsonDocumented DRY wrinkle; plan keeps repo config minimal on top of baseline
config.js syntax/typo breaks all accounts at onceSingle global blast radius — validate render (helm template) before merge. Blast-radius timing: a malformed config.js renders an updated ConfigMap; the pod picks it up on next restart/sync (the chart stamps a checksum/config.jsFileConfigMap annotation that rolls the Deployment). A config that parses but is semantically wrong could affect all three accounts until reverted; a config that fails CE startup leaves CE down (no PRs anywhere) until reverted. Rollback = revert the cluster-repo PR; ArgoCD re-syncs the prior ConfigMap. Validate before merge to keep the window at zero.
Onboarding-PR noise on first seanb4t batchIncremental install; onboarding PRs are opt-in-to-merge, not auto-applied
Effective-config change silently alters fzymgc-houseStep 1 verification: baseline must reproduce current fzymgc-house behavior
  • The fzymgc-house migration itself (done; epic hl-2ctz closed).
  • Any new cluster infrastructure (same pod / App / webhook serve all accounts).
  • A shared preset repo (github>fzymgc-house/renovate-config) — explicitly rejected in favor of inline config.js (D2); may be revisited if the DRY wrinkle becomes painful.
  • Changing PVC size, memory limit, or replica count (D-none; watch-item only).

None blocking. The DRY-wrinkle alignment between config.js and the cluster repo’s renovate.json is a plan-level detail, not a design decision.