Disclosure: I built Publy, the tool this post ends up recommending. It started as a fix for my own problem, not a product — and this is the honest version of how it happened.
If you've ever tried to share private Dart or Flutter code across more than one repo, you already know the two roads in front of you, and you already know both of them hurt.
Road one: a paid private pub registry. It works, dart pub is happy, and the bill climbs every time your team or your package count grows.
Road two: git dependencies. It's free, and it quietly turns your pubspec.yaml into a minefield.
I spent a long time on both. Here's what actually went wrong, and what I did about it.
The git-dependency trap
For a while my team went the free route — pull internal packages straight from git:
dependencies:
app_core:
git:
url: git@github.com:our-org/app-core.git
ref: main
This looks fine on day one. It falls apart on week three.
The moment you reference a package by branch or tag instead of a version, you throw away everything pub's version solver does for you. There are no version constraints — ^1.4.0 means nothing when you're pinned to ref: main. Two apps depending on the same internal package at two different commits? The solver can't reconcile that, because as far as it's concerned they're the same unversioned thing.
So you end up doing this:
dependency_overrides:
app_core:
git:
url: git@github.com:our-org/app-core.git
ref: a3f9c21 # the "known good" commit, do not touch, nobody remembers why
dependency_overrides is meant to be a temporary, local escape hatch. When it becomes the only way your dependency graph resolves, that's not a workaround anymore — that's your architecture, and it's a bad one. Every new package multiplied the pain. Across two internal SDK monorepos we had 50+ packages depending on each other, and the git approach turned every version bump into a manual archaeology dig through commit hashes.
The whole point of semantic versioning and pub's solver is that you don't do this. Git dependencies opt you out of the one thing that makes Dart's package management good.
The paid-registry trap
So we did the sensible thing and moved to a hosted private registry. And it was genuinely better — real versions, dart pub publish, the solver working again.
Then the invoice showed up.
I won't name numbers, but the pricing scaled per seat, and even with a small team of 3 developers plus CI/CD, hosting our internal packages had become one of those line items you keep meaning to "look into." For a handful of private packages that just needed a place to live, the cost-to-value ratio stopped making sense. I was also paying out of pocket to host packages for my own side projects — and that math was even harder to justify.
I did not want a fancy product. I wanted the boring thing: a place to dart pub publish internal packages, with real versions, that didn't charge me per human.
So I built Publy
I'm a Flutter dev, not a SaaS founder, and I built this because the alternatives annoyed me — so it's deliberately small. Publy is a private Dart & Flutter package registry that implements the standard pub hosted repository spec. That last part matters: there's no custom CLI, no plugin, no wrapper. dart pub publish and dart pub get work exactly like they do against pub.dev, because to dart it is just a hosted registry.
Setup is three commands and one pubspec block.
1. Authenticate the CLI (after signing in with GitHub and creating an org + token):
dart pub token add https://publy.dev/o/your-org
2. Tell a package where to publish — in that package's pubspec.yaml:
publish_to: https://publy.dev/o/your-org
dart pub publish
3. Consume it from another repo like any hosted dependency — real version constraints, back at last:
dependencies:
app_core:
hosted: https://publy.dev/o/your-org
version: ^1.4.0
That ^1.4.0 is the whole reason this exists. The solver is doing its job again. No ref, no commit hash, no dependency_overrides graveyard.
What it is and isn't
Being honest about scope, since I hate posts that oversell:
- It's free. Not free-trial, not free-tier-with-an-asterisk — the plan you'd use for a small team costs nothing. I built it so I'd stop paying for this, so charging small teams would defeat the point.
- Auth is GitHub OAuth for the web UI and bearer tokens for the CLI / CI.
-
Versions are immutable — republishing the same
name@versionis a 409, never a silent overwrite. - It's young. No per-package ACLs, no download analytics, no billing gymnastics. If you need enterprise compliance features today, this isn't that yet.
If you're a small-to-medium Flutter team quietly bleeding money on a private registry, or worse, drowning in git-dependency overrides — this is aimed squarely at you.
Try it
You can be publishing in about five minutes: publy.dev, and the getting-started guide is the same three commands above with pictures.
If you try it and something's broken or missing, tell me — genuinely. It exists because I hit a wall, and the fastest way to make it good is other people hitting different walls.
What's your current setup for private Flutter packages — paid registry, git deps, or something cleverer? Curious what everyone else landed on.
Top comments (0)