Multi-Account Renovate CE Rollout — Design
Bead: hl-rn4i Date: 2026-06-15 Status: Reviewed — READY (design-reviewer, round 2)
Summary
Section titled “Summary”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:
- Widen
mendRnvAutoDiscoverFilterfromfzymgc-house/*to all three accounts. - Populate the currently-empty
config.js(renovate.config→ ConfigMaprenovate-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".
Current state
Section titled “Current state”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-appsecret (Vaultsecret/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.jsis an empty stub. Onboarding currently rides stock Renovate defaults (onboardingConfig = config:recommended). There is no ConfigMap file inargocd/app-configs/renovate/; the chart renders the ConfigMap from therenovate.configHelm value, which is unset.- Autodiscover scope is
mendRnvAutoDiscoverFilter: "fzymgc-house/*"in the HelmvaluesObject(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)”| Account | Source repos | Forks | Existing Renovate config |
|---|---|---|---|
| fzymgc-house | org-wide (live) | — | mature .github/renovate.json |
| seanb4t | 49 | 77 | 5 of 49 |
| holomush | 1 (Go) | 0 | unknown |
The 77 seanb4t forks are the principal hazard: any broad autodiscovery would
attempt to onboard them. No preset/.github repo exists in any account.
Decisions
Section titled “Decisions”These were settled during brainstorming (hl-rn4i):
| # | Decision | Choice |
|---|---|---|
| D1 | Governance model | Hybrid: autodiscoverFilter as outer fence (all 3 accounts) + App install scope as inner gate |
| D2 | Policy plane | Inline config.js baseline — no separate preset repo |
| D3 | Per-account policy | Same full baseline for all 3 accounts (no per-account packageRules) |
| D4 | Fork handling | forkProcessing: "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) |
| D5 | Onboarding | Onboarding PRs ON — a repo is not managed until its onboarding PR is merged |
Grounding notes (Rule 7)
Section titled “Grounding notes (Rule 7)”- Renovate
forkProcessing(deepwikirenovatebot/renovate): autodiscover skips forks by default, but the Mend-App “Selected repositories” install scope flips the default toenabledfor selected repos. An explicitforkProcessing: "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 ownrenovate.jsonsets"forkProcessing": "enabled"will override the globaldisabledand 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’smendRnvAutoDiscoverFilter), 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 isrenovate.config(a stringified JS file). It renders to a ConfigMap (data keyconfig.js) mounted at/usr/src/app/config.jsviasubPath; withfullnameOverride: renovate-cethe ConfigMap name isrenovate-ce-config-js. The chart stamps achecksum/config.jsFileConfigMapDeployment 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.jsis the right home for global defaults, but “global” does not mean “unoverridable”: any option a repository may legally set (includingforkProcessing) can be overridden in that repo’s ownrenovate.json. Truly operator-only options are the global-only set (e.g.autodiscover*,onboarding), which repos cannot touch.
Design
Section titled “Design”Governance — defense in depth (D1)
Section titled “Governance — defense in depth (D1)”| Account | App install scope | Rationale |
|---|---|---|
| fzymgc-house | All repos (org-wide, already live) | maintained infra org |
| holomush | All repos (1 repo) | trivial; org-wide is fine |
| seanb4t | Selected repositories (repo-by-repo) | curated membership |
Three layers decide whether a repo is managed, and they compose:
- Outer fence —
mendRnvAutoDiscoverFilter: "fzymgc-house/*,seanb4t/*,holomush/*". Nothing outside the three accounts is ever discovered. - Inner gate — App install scope. For seanb4t, only explicitly-installed repos are visible to CE.
- 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.
Policy plane — config.js (D2, D3)
Section titled “Policy plane — config.js (D2, D3)”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). (autodiscoverFilteritself stays in the chart’smendRnvAutoDiscoverFilterenv 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_VERSIONregex 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.
Accepted DRY wrinkle
Section titled “Accepted DRY wrinkle”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).
Scale validation (D-none; watch-item)
Section titled “Scale validation (D-none; watch-item)”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.
Files touched
Section titled “Files touched”| File | Action | Change |
|---|---|---|
argocd/cluster-app/templates/renovate.yaml | Modify | Widen 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.
Rollout sequence
Section titled “Rollout sequence”- Land config (this cluster repo, one PR): add
config.jsbaseline + fork guard viarenovate.config; widenmendRnvAutoDiscoverFilterto the three accounts. ArgoCD syncs. Verify fzymgc-house behavior is unchanged (the baseline should match current effective config). - holomush: install the App org-wide (1 repo). Verify an onboarding PR appears and is correct.
- 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.
Risks & mitigations
Section titled “Risks & mitigations”| Risk | Mitigation |
|---|---|
| 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.json | Documented DRY wrinkle; plan keeps repo config minimal on top of baseline |
| config.js syntax/typo breaks all accounts at once | Single 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 batch | Incremental install; onboarding PRs are opt-in-to-merge, not auto-applied |
| Effective-config change silently alters fzymgc-house | Step 1 verification: baseline must reproduce current fzymgc-house behavior |
Out of scope
Section titled “Out of scope”- 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 inlineconfig.js(D2); may be revisited if the DRY wrinkle becomes painful. - Changing PVC size, memory limit, or replica count (D-none; watch-item only).
Open questions
Section titled “Open questions”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.