A ten-node fleet deploying a 1.2 GB container image pulls roughly 12 GB across its registry link — for one deploy. Twenty deploys a month makes that 240 GB of identical bytes, and a 30-node fleet pushing a 3 GB image pays 90 GB per deploy before a single container starts. With peer-to-peer distribution, the origin serves the image roughly once — about 1.2 GB — and the nodes trade pieces among themselves over the fast local network. Alibaba, where Dragonfly was born, reports distribution up to 57 times faster than native pulls with registry outbound traffic cut by over 99.5%.
Those three numbers are the whole case for P2P image distribution on a self-hosted fleet, and the rest of this post is the receipt: what the tax actually costs at different fleet sizes, how Dragonfly's pull path works, what CNCF's August 13, 2026 lightweight deployment removes, and when a simpler alternative wins.
One correction up front, because the folk version of this math is wrong in the optimistic direction. Containerd already shares unchanged layers on a node, so a redeploy that touches only the top layer does not re-pull the whole image — the per-deploy tax below is the conservative upper bound, and it bites hardest exactly where fleets feel it: cold nodes after scale-out, base-image bumps that invalidate shared layers, node replacements, and multi-app fleets where every node runs a different mix.
P2P does not beat the layer cache; it beats the N-times-one-download pattern that the layer cache cannot fix.
The per-deploy tax, at three fleet sizes
Here is the comparison the title promises: origin egress per deploy, naive pulls versus P2P, across small, medium, and larger self-hosted fleets and three realistic image sizes — a 400 MB slim runtime, a 1.2 GB typical buildpack-built app image, and a 3 GB heavy image with ML or browser-tooling layers. Naive assumes cold nodes (the scale-out / base-bump case); P2P assumes the origin serves each unique layer once.
| Fleet | 400 MB image | 1.2 GB image | 3 GB image |
|---|---|---|---|
| 3 nodes, naive | 1.2 GB | 3.6 GB | 9 GB |
| 3 nodes, P2P | ~0.4 GB | ~1.2 GB | ~3 GB |
| 10 nodes, naive | 4 GB | 12 GB | 30 GB |
| 10 nodes, P2P | ~0.4 GB | ~1.2 GB | ~3 GB |
| 30 nodes, naive | 12 GB | 36 GB | 90 GB |
| 30 nodes, P2P | ~0.4 GB | ~1.2 GB | ~3 GB |
The naive row grows with the fleet; the P2P row does not — origin egress stays flat as nodes are added, which is the entire economic argument. Roll it up monthly at a modest 20 deploys: the 10-node fleet with 1.2 GB images moves about 240 GB a month of duplicate bytes naive versus ~24 GB with P2P. At 30 nodes and 3 GB images, it is 1.8 TB a month versus ~60 GB.
Three caveats keep this honest. First, warm nodes with shared base layers already pay far less than the naive row — buildpack fleets benefit most because every app shares the same run-image base, so one peer holding it serves the whole fleet. Second, the first pull of a new image still crosses the origin link; P2P amortizes, it does not eliminate.
Third, on generous cloud egress (Hetzner includes 20 TB a month on cloud servers) the money cost is small — the binding constraints are wall-clock deploy time on thin uplinks, Docker Hub's rate limits (100 pulls per 6 hours per IP unauthenticated, 200 for free authenticated accounts — a whole NAT-ed fleet shares one quota), and the self-hosted registry box that falls over when 30 kubelets pull at once.
How Dragonfly's P2P pull actually works
Dragonfly is a CNCF Incubating project, and it is not pure peer-to-peer — it keeps a coordinator and makes each node a piece-swapping peer, BitTorrent-style. Three pieces move data:
- Client (
dfdaemon), a DaemonSet on every node. It acts as a proxy: containerd is configured to route image pulls through the local daemon, which fetches the image in small pieces — some from the origin or seed, most from peers that already hold them. - Scheduler, a StatefulSet. The coordinator. It tracks which peers hold which pieces and tells each client where to fetch, so thirty nodes converging on one image fan out across each other instead of queueing on the registry.
- Seed Client, a StatefulSet. The root peer: it downloads directly from the origin repository and seeds the swarm, so the registry effectively serves one full copy plus change deltas.
The key property for a deploy path: once any node holds a layer, every other node can pull that layer from its peer over the cluster network instead of going back to the registry or upstream. For a git-push platform where every app image shares buildpack base layers, the second deploy of the day barely touches the origin at all.
What "without the database" concretely removes
The reason small teams never adopted this architecture is that a standard Dragonfly installation drags a control plane behind it: a Manager backed by MySQL and Redis. The Manager hosts the web console, exposes OpenAPIs for integrations like registry-triggered preheating, manages multi-cluster relationships, and pushes dynamic configuration to Schedulers and Clients — with MySQL persisting state and Redis caching plus distributing async jobs. That stack makes sense for a platform team running Dragonfly across a multi-cluster fleet.
It is absurd overhead for a single cluster whose only problem is registry overload during image pulls.
The August 13, 2026 lightweight deployment, written up by maintainer Wenbo Qi, deletes exactly that stack. With manager.addr unset, the Scheduler and Client run autonomously: dynamic configuration (scheduling limits, blocklists, endpoint details) loads from a local dynconfig.yaml mounted via a ConfigMap, with defaults generated on startup when the file is absent. The ConfigMap reloads on a refresh interval defaulting to one minute, and the same parameters are exposed as Helm values (scheduler.dynconfig, seedClient.dynconfig, client.dynconfig), so configuration stays declarative and GitOps-aligned.
Scheduler discovery drops back to a native primitive too: the Client resolves the Scheduler's headless Service over DNS, health-checks each endpoint, and follows StatefulSet scaling automatically — static IPs remain available via scheduler.addrs for schedulers outside the cluster.
The in-cluster footprint becomes three workloads and nothing else: Scheduler StatefulSet, Seed Client StatefulSet, and the Client DaemonSet, where dfinit wires containerd to route pulls through the local peer. No database backups, no migration scripts on upgrade — peer state lives in local disk caches rebuildable from the origin on demand. The Helm chart now disables Manager, MySQL, and Redis by default, making lightweight the baseline rather than the special case.
What you give up is explicit and worth tabulating, because the CNCF post publishes the full matrix:
| Capability | Lightweight | Lightweight + Redis | With Manager |
|---|---|---|---|
| P2P task distribution | Yes | Yes | Yes |
Blocklists, CLI preheat via dfctl | Yes | Yes | Yes |
| Persistent tasks / persistent cache metadata | No | Yes | Yes |
| Web console, OpenAPI, personal access tokens | No | No | Yes |
| API-triggered preheat jobs | No | No | Yes |
Read that as a decision rule: if preheating from CI scripts (dfctl) plus raw distribution covers your deploy path, lightweight is complete. The moment you want a registry webhook to trigger preheats through an API, or a console showing fleet-wide distribution state, you are paying for the Manager tier — and its MySQL and Redis with it. The middle column exists for the team that wants task persistence across Scheduler restarts without adopting the whole control plane.
Dragonfly-lightweight vs Spegel vs Kraken vs a plain mirror
Dragonfly is not the only answer, and for the smallest fleets it is not the simplest. The honest alternatives, ranked by operational weight:
- Spegel: the zero-component option. Each node runs a registry mirror via DaemonSet that serves whatever already sits in that node's containerd image store; peer discovery runs over a Kademlia DHT, so there is no scheduler, no database, no state at all — a failed node simply rejoins and re-announces. Spegel's own documentation frames itself explicitly against Dragonfly's old Redis-plus-MySQL requirement, and the August 2026 lightweight post reads as Dragonfly's answer to that critique. Pick Spegel when the fleet is small, homogeneous, and warm — its weakness is the cold-start case, where no peer holds the image and every node falls back to the origin independently, with no seed peer to serve exactly one copy.
- Dragonfly lightweight: the coordinated option. The Scheduler plus Seed Client close Spegel's cold-start gap — the seed guarantees one orderly origin copy — and piece-level scheduling uses LAN bandwidth better on large images. The price is three StatefulSets/DaemonSets to operate instead of one, plus containerd reconfiguration via
dfinit. Pick it when deploys routinely land on cold nodes (autoscaling, spot turnover, base-image bumps) or images are gigabytes. - Uber Kraken: the heavyweight. Origin plus tracker plus per-node agents, proven at Uber scale, but a separate system to learn and run for a problem Dragonfly and Spegel solve inside the cluster. Only relevant past the scale where a single Scheduler becomes the bottleneck — not a Hetzner-fleet problem.
- A plain pull-through registry cache: the dumb option. One in-cluster registry mirror absorbs Docker Hub rate-limit pressure and keeps pulls on the fast network, but every node still downloads its own full copy through it — it fixes quota and latency, not the N-times-one-download math. Fine under ~5 nodes; above that, it is a cache, not distribution.
The shape of the decision: under a handful of warm nodes, a pull-through cache or Spegel wins on simplicity. Past ~10 nodes, gigabyte images, or frequent cold pulls, Dragonfly lightweight's seed-plus-scheduler earns its extra components. Nothing in this list requires the Manager tier until API-driven preheat or a console becomes a requirement rather than a nice-to-have.
What it takes on a git-push fleet
Concretely, the lightweight install is a single Helm command with Manager, MySQL, and Redis omitted — they are disabled by default. The values you actually write are small: image pins for Scheduler, Seed Client, and Client, metrics flags, and the dfinit block that points at containerd's config and proxies all registries through the local peer:
client:
dfinit:
enable: true
config:
containerRuntime:
containerd:
configPath: /etc/containerd/config.toml
proxyAllRegistries: trueThree integration points matter for a deploy path. First, preheat from CI: dfctl can push the freshly built image into the P2P swarm right after the build finishes, so by the time the rollout reaches the tenth node, nine peers already serve it — this replaces the registry-webhook preheat that needs the Manager tier.
Second, seed placement: the Seed Client should sit where origin bandwidth is cheapest and most reliable, ideally beside the registry itself, since every new layer crosses that link exactly once through it. Third, buildpack-layer affinity: shared run-image bases are the best case for piece sharing, so standardizing apps on one or two base images multiplies the savings beyond the per-image table above.
And the gotchas, stated plainly. The first pull of a genuinely new image still traverses the origin link at origin speed — P2P hides the fan-out, not the seed download. Peer caches consume node disk proportional to the working set of images times replicas, so size the Client cache directory against your largest plausible image set, not your average. Private registries still need their credentials configured for the seed and peer pulls; P2P changes the transport, not the auth.
And on clusters where every node already holds every image — tiny static fleets — this entire stack is overhead with no payoff; the table in section one tells you exactly when you cross that line.
Pull once, share everywhere
The pattern behind this post generalizes beyond one CNCF project: any fleet that downloads identical bytes to every machine — container layers today, model weights tomorrow (Dragonfly already distributes OCI-packaged models, and the same swarm logic applies) — eventually stops paying the per-node download tax and starts paying a fixed one-copy cost plus LAN-speed sharing.
What changed in August 2026 is the entry price: the database-backed control plane that used to gate this architecture is now opt-in rather than mandatory, which moves P2P distribution from "platform-team project" to "an afternoon Helm install evaluated against one table of bandwidth math." Run your fleet's numbers through that table. If the naive row hurts, the swarm is waiting.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.
Sources
- CNCF blog, "Lightweight Dragonfly Deployment: P2P Distribution Without the Database Stack," Wenbo Qi, August 13, 2026 — lightweight architecture, dynconfig/ConfigMap mechanism, headless-Service discovery, three-model feature matrix, kind walkthrough.
- Alibaba Cloud blog, "Deploying Tens of Thousands of Servers in Minutes with Container Technology" — up to 57x faster distribution, over 99.5% registry outbound-traffic reduction at Double 11 scale.
- Spegel project documentation (spegel-org/spegel) — stateless OCI mirror design, Kademlia DHT discovery, "Dragonfly requires both Redis and MySQL" critique.
- The New Stack, "Spegel: A Stateless Cache for Locally Storing Image Artifacts" — three-component architecture (registry, routing/discovery, advertising).
- Dragonfly docs, containerd mirror mode (d7y.io) —
dfdaemonproxy wiring and registry endpoint configuration.



