One Core, Many Brands: Killing Configuration Drift with One-Way Generation
June 8, 2026 · 7 min read
If you have ever run more than one version of basically the same website, you know how it starts. You build the first one. It works. Someone wants a second that is 95% identical: same features, same layout, different name, different colours, different content. So you copy the repo, change what needs changing, and ship it. Then you do it a third time, and a fourth.
Six months later you have five "identical" sites that are not. A security patch went into two of them. A feature landed in one. A third has a hotfix nobody else got. Every change is now five pull requests, five deploys, and five chances to forget one, and forgetting one is exactly how the sites quietly rot apart. That is configuration drift, and copy-paste forking guarantees it. The cost is invisible on day one and compounds every week after, and by the time it genuinely hurts, untangling five divergent codebases back into one is a project nobody wants to fund.
Why the obvious fixes do not hold
The first instinct is a shared component library or an npm package. You should do that anyway, but it does not solve the real problem. Each brand still owns a full, editable copy of the whole application: its routes, its build config, its server, its glue code. A shared button component does not stop someone from quietly editing brand C's checkout flow at 2am during an incident. As long as a brand can be hand-edited, eventually it will be, and you are back to drift, now with the false confidence that you had already solved it.
The second instinct is a runtime config: one deployment that reads a brand id and behaves differently. That holds until brands need different builds, different dependencies, or to be deployed and scaled independently, at which point a single runtime becomes a single point of failure and a thicket of conditionals. The pattern below gives you per-brand artifacts without per-brand codebases.
The shape of the fix
Split the system in two. A single shared core contains the entire application: the app, the API, the build tooling, the deployment scripts. Thin per-brand overlays contain only what genuinely differs. No brand contains application code; it contains the handful of values that make it that brand.
What actually lives in an overlay
It helps to be concrete, because the discipline lives or dies on keeping overlays ruthlessly thin. A brand overlay holds its identity (name, domains, support address), its look (a palette, fonts, a logo, maybe a hero image), its content (copy, legal pages, the genuinely per-brand text), and a set of feature flags that toggle optional pieces of the core on or off. That is the whole budget. The moment something that looks like logic wants to live in an overlay, a special checkout rule or a bespoke integration, that is your signal to add a capability to the core instead and expose it as a flag. Overlays describe what a brand is. The core decides how anything is done.
Generation only flows one way
Here is the part that matters most. A build step composes the core with one brand's overlay and produces a standalone, deployable artifact: a complete, runnable repo for that brand. That artifact is disposable. You never edit it by hand, ever. To change a brand you edit its overlay or the core, and you regenerate.
The discipline is the whole point. Generation is strictly one-directional, from core to generated, and the merge is deterministic, so the same core and overlay always produce the same output. Because nobody edits the output, no brand can fork the core. Drift stops being something you police with code review and process, and becomes something the architecture makes structurally impossible. That is the difference between "please do not edit the generated folder," which fails the first time someone is in a hurry, and "editing it does nothing, because the next generation overwrites it," which holds forever.
Adding a brand becomes paperwork
Once this is in place, launching a new brand is a configuration exercise, not an engineering project. You create an overlay (a name, a domain, a palette, the content), run the generator, and you have a deployable site that is, by construction, current with every fix and feature in the core. A bug fixed once is fixed everywhere the next time you generate. The marginal cost of brand number ten is almost nothing, which is the entire economic point of running a family of brands in the first place.
Guardrails that keep it honest
Two more pieces make it safe to run in production. Verification gates regenerate and diff the output against what is actually deployed, gitignore-aware so build noise does not drown the signal, and refuse to ship on an unexpected difference. If someone did manage to hand-edit a deployed artifact, the gate catches the divergence instead of silently overwriting the evidence. And delivery is data-safe: when output syncs to a deploy target it excludes secrets, git history, and backups, so a regeneration can never clobber live data or leak credentials into a generated repo.
The whole thing lives in a monorepo, containerized, and shipped through CI, so "regenerate and deploy every brand" is one pipeline rather than a manual ritual you perform N times and get wrong once.
Adopting it when you already have forks
You almost never get to start here. You arrive with five divergent repos that were copy-pasted two years ago, and the migration is harder than the architecture. The approach that works is to pick the healthiest fork as the seed for the core, then diff every other fork against it and sort each difference into one of two buckets. Either it is a genuine per-brand value, in which case it becomes overlay data, or it is a divergence that should never have happened, in which case it gets reconciled into the core by choosing the correct behaviour once. It is tedious, and it is where you find out exactly how far the brands had drifted, which is usually further than anyone believed. But you pay it once, and you come out with a single source of truth instead of five liabilities.
What you give up
This is not free. You lose the ability to quickly hand-tweak a single brand, and sometimes you genuinely want to. The answer is that a one-off has to become a first-class option in the overlay system: a feature flag, a content slot, a config switch. That is more work than a quick edit, and it is the tax you pay for never drifting again. You also invest up front in the generator and the diffing before you see any payoff, which is a hard sell at two brands and an obvious win at ten.
Which points at when not to do this. If you will only ever have two or three brands, or if the brands are going to diverge into genuinely different products, do not force them through one core. You will spend more energy fighting the abstraction than drift would ever have cost you. The pattern earns its keep when you have a real family of near-identical products and the number is going up.
Enjoyed this? Let me know