Because mixing them lets public packages land next to your internal ones, and lets anyone who can write to the mirror alter what everybody installs. Keep a hosted repository for your internal packages and a proxy repository for public ones, and give clients a single read address that combines them in a defined order.
Three kinds of repository
Most repository managers offer the same three building blocks, under different names.
| Type | What it holds | Who writes |
|---|---|---|
| Hosted | Your internal packages | Your CI only |
| Proxy | Cached copies of a public registry | The mirror itself, on demand |
| Group or virtual | A read view combining the others in a set order | Nobody, it is a view |
Developers and CI read from the group. Only CI publishes, and only to the hosted repository.
What the split buys you
First, a clear write path. Only a handful of service accounts need write permission on the hosted repository, and nobody needs it on the proxy.
Second, predictable resolution. In the group, put the hosted repository first. For names you own, the internal copy answers before the public one is consulted. Some ecosystems also let clients restrict which repository may serve which names, which is stronger than ordering alone. Earlier posts in this series cover source mapping for NuGet and filters for Composer.
Third, easier cleanup. Retention rules for a proxy cache are very different from rules for artifacts you must keep.
Publishing is a separate configuration
Build tools usually treat publishing as separate from resolution. Gradle's documentation on mirrors states that mirrors apply to resolution only, and that publishing always uploads to the repository URL declared in the build. That is a useful reminder: pointing resolution at the group does not move uploads, so check where each tool publishes, and give it the hosted repository address with a write credential that only CI holds.
Steps to set it up
- Create the hosted repository and restrict write to a CI service account.
- Create a proxy repository for each public registry you use.
- Create the group with the hosted repository first.
- Point clients at the group address for reads.
- Point publish steps at the hosted address with a dedicated token.
- Test that a developer account cannot upload to the group or the proxy.
- Test that a public package with the same name as an internal one does not win.
Common mistakes
Allowing redeploy of released versions in the hosted repository is a frequent one. A released version should be immutable, so a fix ships as a new version. Another is giving the proxy repository a write role for convenience. Finally, teams sometimes leave the group's order unreviewed after adding a new repository, which can put a public source ahead of an internal one.
Where Safeguard fits
Safeguard's policy gates can check dependency changes in pull requests, which complements repository layout by catching names that do not belong. A free scan is at gold.safeguard.sh/scan.
Try it yourself
Click through it at your own pace, or open it full screen.
Watch how a gate holds a change for review, and think about which publish steps in your pipeline deserve the same hold.
Quick check
Can developers publish test packages to the hosted repository?
Give them a separate sandbox repository so experiments never sit beside released internal packages.
Is ordering enough to stop dependency confusion?
It helps, but client side filters or source mapping are stronger because they do not depend on order.
Sources
Last reviewed: 19 October 2026.