Clone chains are one of those ZFS features that feel like a magic trick right up until the day you can’t destroy a snapshot, can’t free space, and your replication job starts hauling around history you swear you deleted last quarter. The tool that gets you out of that knot is zfs promote. It’s not glamorous, but it’s the difference between “I’ll clean this up after lunch” and “Why is the on-call phone melting?”
This article is written from the production side of the rack: the side where datasets have owners, SLAs have teeth, and you discover interesting new clone dependencies during an incident bridge. We’ll go deep enough to be precise, but not so academic that you can’t use it at 3 a.m.
What zfs promote actually does
In ZFS, a clone is a writable dataset created from a snapshot. The snapshot is the “origin” for that clone. Under the hood, the clone initially shares blocks with its origin snapshot, and diverges as it writes new data (classic copy-on-write behavior).
The key operational consequence: a clone creates a dependency. As long as the clone exists, the origin snapshot cannot be destroyed, because ZFS still needs it to represent the clone’s starting point. That’s why you sometimes run into the “can’t destroy snapshot: snapshot has dependent clones” wall.
zfs promote is the escape hatch: it reverses the dependency between a clone and its origin. After promotion, the clone becomes the “main line” dataset, and the old parent becomes the dependent clone (or more precisely: the origin relationship flips so the promoted dataset no longer depends on the old origin snapshot).
Think of it as changing which dataset is considered the ancestor in the origin relationship. You are not copying data around; you’re changing which snapshot is considered the origin reference point for the clone relationship. That’s why promotion is fast: it’s metadata surgery, not a data rewrite.
One sentence you can repeat to yourself during a maintenance window: promote changes who depends on whom; it does not “merge” datasets.
First joke (short, and painfully accurate): If you’ve never said “it’s just a clone” and immediately regretted it, congratulations on your peaceful and suspiciously short storage career.
Key terms you need in your head
- Snapshot: read-only point-in-time view of a dataset.
- Clone: writable dataset created from a snapshot.
- Origin: the snapshot a clone was created from (visible via the
originproperty). - Clone chain: when a clone has snapshots, and new clones are made from those snapshots, forming a dependency graph.
- Promotion: reversing the origin dependency so the promoted dataset is no longer blocked by an older ancestor snapshot.
Clone chains: the family tree you didn’t mean to start
A simple case looks like this:
pool/apphas snapshotpool/app@baselinepool/app_cloneis created frompool/app@baseline
At this point, pool/app@baseline is pinned. You can’t destroy it while pool/app_clone exists, because that snapshot is the origin that defines the clone’s initial state.
Now add time and human creativity. Someone takes snapshots of the clone. Someone creates another clone from the clone. Someone renames datasets. Someone replicates to a DR pool. The simple two-node dependency becomes a small ecosystem.
In production, clone chains tend to happen in three predictable places:
- VM templates and golden images: a base dataset snapshot spawns dozens of clones (VM disks), then new “templates” are cloned from those clones, because it was convenient.
- CI/CD environments: ephemeral clones created per build, but the base snapshots are kept “just in case,” and later become business-critical.
- Database refreshes: a prod snapshot is cloned into a staging environment, staging becomes “the place where fixes happen,” and suddenly staging has snapshots and children that must be retained.
Here’s the operational trap: clone chains can make the pool look like it’s leaking space. You delete old snapshots, but “USED” barely moves. You destroy what you think is the last dependent dataset, and a different one still pins the origin snapshot. Meanwhile, your retention policy is silently becoming “forever.”
Why promotion matters in real life
Promotion is how you:
- Free an older dataset to be deleted while keeping the newer dataset.
- Recenter a workload’s lineage on the dataset that actually matters now.
- Clean up a snapshot tree where the “parent” dataset is dead, but its snapshot still blocks everything.
- Make replication workflows saner by stabilizing which dataset is the authoritative source.
Interesting facts & historical context
- ZFS clones predate the modern container boom: clones were already a practical way to spin up writable copies fast, long before “copy-on-write layers” became a buzzword in other ecosystems.
- Promotion exists because deletion is more valuable than creation: cloning is easy; cleaning up safely is what required a first-class command.
- The “origin” is a property, not a feeling: ZFS records the origin snapshot explicitly, which is why you can query and reason about dependencies instead of guessing.
- Clone chains are graphs, not lists: one snapshot can have multiple clones; each clone can have its own snapshots and clones. People say “chain” because it feels linear until it isn’t.
- Space is held by referenced blocks, not by your intention: deleting a snapshot only frees blocks not referenced elsewhere. Clones are “elsewhere.”
- Promotion is typically metadata work: it’s fast because ZFS is adjusting lineage bookkeeping, not copying terabytes. (It can still take time on busy systems due to syncing and transaction groups.)
- Renaming doesn’t change ancestry:
zfs renamechanges names; it does not change the origin relationship. This is how “we renamed it, so it’s new” becomes a storage incident. - Snapshot retention policies often ignore clones: homegrown retention scripts commonly destroy snapshots oldest-first, then crash into a dependent clone and stop—or worse, partially succeed and leave a mess.
When you should promote (and when you shouldn’t)
Promote when…
- You want to delete the original dataset (or its old snapshots), but clones depend on it.
- A clone has become the real workload, and the original dataset is now “just history.”
- You inherited a pool where the current production dataset is a clone of a clone of a snapshot from 2019, and your retention is basically a museum exhibit.
- You need to break a dependency so snapshot destruction or replication pruning can proceed.
Don’t promote when…
- You’re not sure which dataset is the authoritative source and replication upstream expects a particular lineage.
- You have automation that assumes a given dataset is the parent and uses
originor naming conventions as logic. - You’re trying to “merge changes back” into the parent. Promotion doesn’t merge; it just rearranges who is the origin.
Second joke (because everyone needs exactly one more): Promoting the wrong dataset is like “reply all” in email—technically impressive, socially expensive, and immediately irreversible in everyone’s memory.
Practical tasks with commands (and what the output means)
These are real operator tasks: “show me the dependency,” “prove which snapshot is pinned,” “promote safely,” “validate after.” Commands assume a pool named tank and datasets like tank/app. Adjust for your environment.
Task 1: Find clones and their origins
cr0x@server:~$ zfs list -t filesystem,volume -o name,origin
NAME ORIGIN
tank/app -
tank/app-clone tank/app@baseline
tank/db -
tank/db-staging tank/db@weekly-2025w51
Interpretation: Any dataset with a non-- origin is a clone. Here, tank/app-clone depends on tank/app@baseline.
Task 2: See which snapshot refuses to die (dependency error)
cr0x@server:~$ zfs destroy tank/app@baseline
cannot destroy 'tank/app@baseline': snapshot has dependent clones
use '-R' to destroy the following datasets:
tank/app-clone
Interpretation: ZFS is telling you exactly what is pinned. You can destroy the clone (-R would recursively destroy), or promote the clone so you can delete the old origin history without deleting the active workload.
Task 3: Display snapshot space and “why is it still used?”
cr0x@server:~$ zfs list -t snapshot -o name,used,refer,creation -s creation tank/app
NAME USED REFER CREATION
tank/app@baseline 0B 5.20G Mon Nov 10 09:12 2025
tank/app@post-upgrade 88M 5.45G Tue Nov 18 14:03 2025
Interpretation: Snapshot USED is space uniquely attributable to that snapshot compared to the live dataset, not total space pinned by its existence. Clones can still force this snapshot to exist even if it “uses 0B.”
Task 4: Check dataset space accounting that points to clones/snapshots
cr0x@server:~$ zfs get -H -o name,property,value used,usedbysnapshots,usedbychildren,usedbydataset tank/app
tank/app used 220G
tank/app usedbysnapshots 12G
tank/app usedbychildren 180G
tank/app usedbydataset 28G
Interpretation: If usedbychildren is huge, children datasets (including clones mounted elsewhere) are keeping space referenced. If usedbysnapshots is huge, snapshot retention is the suspect.
Task 5: Find where the clone is mounted (so you don’t promote the wrong thing)
cr0x@server:~$ zfs get -H -o name,value mountpoint tank/app tank/app-clone
tank/app /srv/app
tank/app-clone /srv/app-staging
Interpretation: This is how you tie a dataset name to a real workload. Before promotion, confirm which one production is actually writing to.
Task 6: Promote the clone (the core operation)
cr0x@server:~$ sudo zfs promote tank/app-clone
Interpretation: After this, tank/app-clone becomes independent of tank/app@baseline in the way you care about operationally: the old “parent” dataset becomes the dependent one in the origin relationship.
Task 7: Verify origins after promotion
cr0x@server:~$ zfs list -o name,origin tank/app tank/app-clone
NAME ORIGIN
tank/app tank/app-clone@baseline
tank/app-clone -
Interpretation: The promoted dataset now has no origin. The old parent now points back to a snapshot on the promoted dataset. This is the flip. This also explains why promotion can create/require a corresponding snapshot reference in the promoted dataset lineage.
Task 8: Now destroy the old snapshot history (the cleanup)
cr0x@server:~$ sudo zfs destroy tank/app@baseline
cannot destroy 'tank/app@baseline': snapshot has dependent clones
Interpretation: If you still see this error, you may have more than one clone, or a deeper graph. Promotion fixes one relationship; it doesn’t automatically dissolve every dependency if multiple clones hang off the same snapshot.
Task 9: Enumerate all clones of a snapshot (dependency mapping)
cr0x@server:~$ zfs get -H -o name,value clones tank/app@baseline
tank/app@baseline tank/app-clone,tank/app-ci-112,tank/app-ci-113
Interpretation: This tells you who pins that snapshot. You’ll only be able to destroy it when none of these exist (or when each has been promoted appropriately and lineage updated).
Task 10: Safely rename after promotion (stabilize naming)
cr0x@server:~$ sudo zfs rename tank/app tank/app-old
cr0x@server:~$ sudo zfs rename tank/app-clone tank/app
Interpretation: Promotion changes ancestry; rename changes the names your tooling and humans use. In practice you often promote, then rename so production stays at the canonical dataset name.
Task 11: Confirm nothing changed in the mountpoints (or fix it)
cr0x@server:~$ zfs get -H -o name,value mountpoint tank/app tank/app-old
tank/app /srv/app
tank/app-old /srv/app-staging
Interpretation: Rename doesn’t always magically do what you want with mountpoints if they were explicitly set. Confirm and set them deliberately.
cr0x@server:~$ sudo zfs set mountpoint=/srv/app tank/app
cr0x@server:~$ sudo zfs set mountpoint=/srv/app-old tank/app-old
Task 12: Validate the “pinned space” effect after cleanup
cr0x@server:~$ zfs list -o name,used,usedbysnapshots,usedbychildren,usedbydataset tank/app-old
NAME USED USEDBYSNAPSHOTS USEDBYCHILDREN USEDBYDATASET
tank/app-old 28G 0B 0B 28G
Interpretation: The old dataset is now a normal dataset (or a clone) without children/snapshot baggage. If you’re retiring it, it’s now straightforward to snapshot/destroy according to policy.
Task 13: Find all clones in a subtree (audit before maintenance)
cr0x@server:~$ zfs list -r -o name,origin tank | awk '$2 != "-" {print}'
tank/app-ci-112 tank/app@baseline
tank/app-ci-113 tank/app@baseline
tank/db-staging tank/db@weekly-2025w51
Interpretation: This is a quick-and-dirty inventory. In larger environments, clone sprawl is rarely documented; you discover it by asking ZFS, not by asking Slack.
Task 14: Check holds that prevent snapshot destruction (different problem, similar symptom)
cr0x@server:~$ zfs holds tank/app@baseline
NAME TAG TIMESTAMP
tank/app@baseline keep Wed Dec 10 10:21 2025
Interpretation: If destruction fails but clones aren’t the reason, a user hold might be. Promotion won’t help you here; you need to release the hold.
cr0x@server:~$ sudo zfs release keep tank/app@baseline
Task 15: Dry-run a “destroy recursively” decision with zfs destroy -nvp
cr0x@server:~$ sudo zfs destroy -nvp tank/app@baseline
would destroy tank/app@baseline
would destroy tank/app-ci-112
would destroy tank/app-ci-113
would reclaim 41.2G
Interpretation: This is how you avoid the career-limiting move of wiping a CI environment that somebody quietly turned into production last year.
Replication and backups: zfs send/receive with clones
Clone chains don’t just complicate deletion; they complicate replication. The moment you rely on incremental sends, you’ve implicitly accepted lineage as a contract: “this dataset on the receiver shares snapshot history with the sender.” Clones introduce forks in that history.
What usually goes wrong
Two classic surprises:
- Replication sends more than expected because you accidentally replicated the wrong branch or kept an origin snapshot alive, so the receiver must retain it too.
- Replication fails with “incremental source does not match” because someone promoted/renamed on one side and not the other, breaking the assumed snapshot lineage.
Promotion itself is not inherently incompatible with replication, but it is a structural change. If you promote a dataset that is being replicated incrementally, treat it like a schema migration: coordinate it, record it, and validate both sides.
Task 16: Inspect snapshot lineage for replication (common base)
cr0x@server:~$ zfs list -t snapshot -o name,creation -s creation tank/app | tail -n 5
tank/app@replica-2025-12-20 Sat Dec 20 01:00 2025
tank/app@replica-2025-12-21 Sun Dec 21 01:00 2025
tank/app@replica-2025-12-22 Mon Dec 22 01:00 2025
tank/app@replica-2025-12-23 Tue Dec 23 01:00 2025
tank/app@replica-2025-12-24 Wed Dec 24 01:00 2025
Interpretation: A stable, predictable snapshot naming scheme is your friend. If your snapshots are ad hoc, clone dependencies become harder to reason about and replicate cleanly.
Task 17: Use a replication stream carefully (example incremental)
cr0x@server:~$ sudo zfs send -I tank/app@replica-2025-12-23 tank/app@replica-2025-12-24 | sudo zfs receive -u backup/app
Interpretation: -I sends all intermediate snapshots. If clone chains and promotions changed the available snapshots, the receiver may not have the expected base. Always confirm the base snapshot exists on both ends before running incrementals.
Task 18: Confirm origin and GUID-like identity thinking (pragmatic check)
cr0x@server:~$ zfs get -H -o name,property,value origin tank/app backup/app
tank/app origin -
backup/app origin -
Interpretation: Replication doesn’t require the receiver dataset to be a clone, but it does require shared snapshots for incrementals. Don’t confuse “origin” (clone relationship) with “shared history” (replication snapshot set). They overlap conceptually, but they’re different mechanisms.
Three corporate-world mini-stories
1) Incident caused by a wrong assumption: “Deleting the snapshot will free space”
In a large, very normal enterprise, an application team ran their own ZFS pool for build artifacts and “temporary” environments. Their retention script was simple: destroy snapshots older than 30 days. When the pool crept toward 90% full, they did what everyone does under pressure: delete more snapshots.
It did nothing. zfs list showed fewer snapshots, but zpool list didn’t budge. The on-call engineer assumed ZFS was “slow to reclaim space,” and scheduled a reboot because—well—sometimes it works on other things.
The reboot didn’t reclaim space. The pool hit a higher watermark, allocations started failing, and a few jobs began producing corrupted outputs (not because ZFS corrupted them, but because upstream tools didn’t handle ENOSPC gracefully). The incident bridge filled with the sound of people discovering that “disk full” is a cross-functional hobby.
The fix was embarrassingly straightforward: the snapshots they were deleting were pinned by clones created for CI jobs. Those clones were kept longer than 30 days because someone liked having “reproducible old builds.” The retention script never checked for clone dependencies and quietly skipped snapshots it couldn’t destroy. It was the worst kind of failure: a silent one.
They audited origins, promoted the few clones that had become permanent environments, and deleted the truly ephemeral clone datasets. Space dropped immediately, and the incident ended with the usual lessons: ZFS did exactly what it promised; humans did not.
2) Optimization that backfired: “Let’s clone everything to save time”
A platform team wanted to speed up database refreshes for QA. They had a tidy process: snapshot production nightly, replicate it, restore to QA. It was slow, and the team had performance pressure. Someone proposed a clever idea: stop restoring; just clone the latest snapshot and hand it to QA as a writable dataset. Refreshes would become instant.
It worked brilliantly for a month. QA loved it. The platform team enjoyed the rare feeling of having solved time. Then storage started looking weird: snapshot retention didn’t free space, replication streams grew, and the DR pool was retaining history far beyond policy. Everyone blamed “ZFS overhead.” Nobody said “clone dependency” out loud until someone tried to delete an ancient snapshot and hit the dependent-clone error.
The real problem was organizational, not technical. The optimization turned QA datasets into long-lived production-like environments with their own snapshot habits. Engineers began creating clones from clones to run parallel tests. The resulting graph pinned months of prod snapshots. The “instant refresh” trick had quietly rewritten the retention contract for the entire system.
They recovered by drawing a hard boundary: ephemeral clones got a TTL enforced by automation, and long-lived QA environments were promoted so they were no longer anchoring prod’s snapshot history. They kept the optimization, but only after adding guardrails: inventory of clone origins, explicit promotion points, and a policy that said “clones are cattle unless explicitly promoted.”
3) A boring but correct practice that saved the day: “Name snapshots like you mean it, and rehearse promotion”
A finance-adjacent team ran a ZFS-backed VM farm. They had the usual complexity: templates, clones for VMs, and a small army of snapshots for patching and rollback. Nothing special—just enough moving parts to make an outage spicy.
Their boring practice was this: every VM disk clone had a tag in its ZFS properties for owner and environment, every template snapshot had a stable name, and once per quarter they ran a maintenance rehearsal where they deliberately promoted a clone in a lab pool and validated that their backup/replication still behaved.
One day, an engineer attempted to retire an old template dataset. Deleting its snapshots failed because dependent clones existed. This is where teams usually panic. But this team’s inventory told them exactly which clones were long-lived and which were ephemeral. They promoted the long-lived ones, then destroyed the rest, and finally removed the old template history.
The best part wasn’t the promotion—it was how unexciting it was. No surprise downtime. No “who owns this dataset?” archaeology. No replication meltdown. The change ticket looked boring, which is the highest compliment you can pay storage work.
Fast diagnosis playbook
This is the “I have 15 minutes before the pool hits 95% and my pager starts composing poetry” sequence. The goal is to identify whether your bottleneck is clones pinning snapshots, snapshot retention, children datasets, or something else entirely.
First: confirm the pool-level problem and its nature
cr0x@server:~$ zpool list
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
tank 7.25T 6.71T 560G - - 38% 92% 1.00x ONLINE -
Interpretation: High CAP is the emergency. Fragmentation can matter, but it’s usually not the first-order cause of “why didn’t deletion free space.”
Second: find the top consumers and whether it’s snapshots or children
cr0x@server:~$ zfs list -o name,used,usedbysnapshots,usedbychildren,usedbydataset -S used | head -n 10
NAME USED USEDBYSNAPSHOTS USEDBYCHILDREN USEDBYDATASET
tank/vm 4.90T 900G 3.80T 200G
tank/app 220G 12G 180G 28G
tank/db 140G 60G 0B 80G
Interpretation: If usedbychildren dominates, your space is in children (often clones). If usedbysnapshots dominates, retention is your suspect. If usedbydataset dominates, the live data is big and you’re not deleting the right thing.
Third: check for clone dependencies pinning old snapshots
cr0x@server:~$ zfs list -t filesystem,volume -o name,origin -r tank/vm | awk '$2 != "-" {print}' | head
tank/vm/qa-01 tank/vm/template@golden
tank/vm/qa-02 tank/vm/template@golden
tank/vm/prod-17 tank/vm/template@2025q3
Interpretation: This tells you whether clones exist and what they pin. If you can’t delete a snapshot, this is usually why.
Fourth: for a specific pinned snapshot, list its clones
cr0x@server:~$ zfs get -H -o value clones tank/vm/template@golden
tank/vm/qa-01,tank/vm/qa-02,tank/vm/ci-881,tank/vm/ci-882
Interpretation: Now you have the to-do list: destroy ephemeral clones, promote the ones that must live on, then delete the old snapshot history.
Fifth: sanity-check I/O pressure before doing heavy operations
cr0x@server:~$ zpool iostat -v 1 3
capacity operations bandwidth
pool alloc free read write read write
-------------------------- ----- ----- ----- ----- ----- -----
tank 6.71T 560G 380 520 42.1M 88.3M
mirror-0 3.35T 280G 190 260 21.0M 44.1M
sda - - 95 130 10.5M 22.1M
sdb - - 95 130 10.5M 22.0M
mirror-1 3.36T 280G 190 260 21.1M 44.2M
sdc - - 95 130 10.6M 22.1M
sdd - - 95 130 10.5M 22.1M
Interpretation: Promotions are usually quick, but cleanup operations that trigger lots of frees can coincide with heavy writes and make latencies worse. If you’re already on the edge, schedule cleanup or throttle workloads.
Common mistakes, symptoms, and fixes
Mistake 1: Trying to destroy the origin snapshot without addressing clones
Symptom: cannot destroy ... snapshot has dependent clones
Fix: Identify dependent clones using zfs get clones pool/ds@snap. Decide: destroy clones (ephemeral) or promote clones (long-lived), then retry snapshot destruction.
Mistake 2: Promoting the dataset you intended to delete
Symptom: After promotion, the “wrong” dataset has origin=- and your automation points to the wrong mountpoint.
Fix: Before promotion, confirm mountpoints and active writers. Use zfs get mountpoint and OS-level checks (which process writes where). If already promoted, you can still recover by renaming datasets and fixing mountpoints; the data isn’t lost, but your lineage changed, so update runbooks and replication assumptions.
Mistake 3: Assuming promotion will free space immediately
Symptom: You promoted a clone but pool usage doesn’t drop.
Fix: Promotion doesn’t delete anything. It only enables you to destroy old snapshots/datasets that were previously pinned. Follow through: destroy the obsolete dataset/snapshots after verifying dependencies are gone.
Mistake 4: Confusing “snapshot USED” with “space pinned by the snapshot”
Symptom: A snapshot shows USED=0B, but destroying it is blocked and/or it seems to pin a lot of space.
Fix: Use zfs get clones and dataset-level usedby* properties to understand space. USED on a snapshot is not a full accounting of what it enables elsewhere.
Mistake 5: Letting clone sprawl become policy-by-accident
Symptom: Old “baseline” snapshots can’t be deleted years later; replication keeps huge histories; nobody knows what can be destroyed.
Fix: Add metadata (properties like owner/env), TTL automation for ephemeral clones, and a quarterly audit: list clones and their origins, identify which should be promoted to become independent lineages.
Mistake 6: Breaking incremental replication by changing lineage unilaterally
Symptom: Incremental send fails because the receiver lacks the expected base snapshot; after promotion, names and snapshot ancestry no longer match expectations.
Fix: Coordinate promotion with replication. Validate common snapshots on both ends. In some cases, you must reseed with a full send, or adjust replication to new baselines.
Checklists / step-by-step plan
Checklist A: “I need to delete an old dataset, but its snapshots are pinned”
- Identify the dataset and the specific snapshots you want gone.
- List clones of those snapshots with
zfs get clones. - Classify each clone: ephemeral (safe to destroy) vs long-lived (must keep).
- For each long-lived clone, confirm it’s the correct dataset to preserve (mountpoint, ownership, active writers).
- Promote long-lived clones (
zfs promote). - Destroy ephemeral clones (prefer
zfs destroy -nvpfirst). - Destroy the now-unpinned snapshots.
- Destroy or archive the retired dataset (after a final sanity snapshot if policy requires).
Checklist B: “I’m promoting a clone that is (or might be) production”
- Freeze change window; announce lineage change to app + backup owners.
- Verify the dataset and mountpoint; verify the running workload uses that mount.
- Take a safety snapshot of both parent and clone.
- Record
zfs list -o name,originoutput before the change. - Run
zfs promote. - Verify origin flip with
zfs list -o name,origin. - If desired, rename datasets to restore canonical naming.
- Validate application I/O, mounts, and replication jobs.
- Only then proceed to delete old parent snapshots/datasets.
Checklist C: “We need to prevent clone chains from becoming a quarterly surprise”
- Define which datasets are allowed to be clone origins (templates, baselines).
- Tag clone datasets with owner/environment using ZFS user properties.
- Automate TTL cleanup for ephemeral clones and their snapshots.
- Run a weekly audit: list all datasets with non-empty
origin. - Review “pinned snapshots”: snapshots with many clones.
- Promote long-lived clones intentionally, not accidentally.
FAQ
1) Does zfs promote copy data?
No. Promotion is primarily metadata work that changes the origin relationship. Your blocks stay where they are; ZFS changes how it accounts for ancestry and dependencies.
2) Will promoting a clone free space immediately?
No. Promotion enables you to delete snapshots/datasets that were previously pinned. Space is reclaimed when you destroy the now-unneeded snapshots/datasets and those blocks are no longer referenced.
3) Can I promote a dataset with children?
In practice, yes, but you need to be careful: children datasets, snapshots, and clones can make the dependency graph larger than you think. Audit the subtree first (zfs list -r -o name,origin) so you don’t promote into a more tangled situation.
4) What happens to the origin property after promotion?
The promoted dataset will show origin=-. The dataset that used to be the parent will typically gain an origin pointing to a snapshot of the promoted dataset (the relationship flips).
5) Is promotion safe on a live system?
It’s commonly done on live systems, but “safe” depends on what else is happening: heavy I/O, replication runs, and automation that reacts to dataset changes can make a simple operation noisy. Promote during a controlled window and validate mounts and tooling afterward.
6) Why can’t I destroy a snapshot even though there are no clones?
Two frequent reasons: a user hold exists (zfs holds), or the snapshot is needed for incremental replication history (not a “blocker” in ZFS terms, but your process may depend on it). ZFS itself will tell you if clones block destruction; holds are separate.
7) Does renaming a dataset help with clone dependencies?
No. Rename changes the name, not the ancestry. If a snapshot is pinned by clones, renaming won’t unpin it. Promotion or clone destruction is what changes the dependency.
8) How do I find all snapshots that have clones?
You can iterate snapshots and check the clones property. Practically, start from the datasets where space is stuck and query their older snapshots for clones. In many environments, it’s faster to first list all datasets with origin set, then map them back to the origin snapshots.
9) Can promotion break my incremental replication?
It can, if your replication workflow assumes a particular snapshot lineage and you change it on the sender without coordinating the receiver. Treat promotion as a lineage change: verify shared snapshots and be prepared to reseed or adjust baselines.
10) What’s the safest way to experiment with promotion?
Do it on a test pool with a small dataset that mimics your structure: create a snapshot, clone it, add snapshots to both sides, create a clone-of-a-clone, then promote and observe origin flips. The muscle memory pays off when you’re doing it under pressure.
Conclusion
zfs promote is one of those commands that looks like a niche corner of ZFS until you realize it’s the key to deleting things safely in a world full of clones. Promotion doesn’t move data; it moves responsibility. It lets you declare, “this dataset is the new truth,” and then cleanly retire the old truth without accidentally bulldozing the present.
The production-grade approach is consistent: inventory clone origins, promote intentionally, validate afterward, and only then destroy the snapshots and datasets you meant to retire. If you do that, clone chains stop being spooky. They become just another tool you can use—confidently—without paying the hidden interest later.