I often talk to developers who have just found out that Torizon OS uses OSTree, and the reaction is almost always the same: a slightly suspicious yet curious look or reply. It makes sense. OSTree isn't a technology most embedded Linux developers grew up with, and anything that touches how your root filesystem is built, updated, and booted deserves a healthy dose of skepticism.
Most of the pushback I hear isn't really about OSTree, though. It's about a handful of myths that stuck around because OSTree looks unfamiliar at first glance, and unfamiliar tends to get translated into "slow", "fragile", or "complicated" before anyone has actually had a deeper look into it.
So let's do the deep dive. I've picked the five objections I hear most often, the ones that come up in sales calls, support tickets, and the occasional heated forum thread, and I'll go through them one at a time.
I'm not going to pretend OSTree has no trade-offs; it does, and I'll be honest about them. But once you see what's actually happening under the hood, most of these worries go away.
Myth 1: "OSTree is Slow"
There's a bit of truth here. When you first set up an OSTree-based system, there is more going on in your development PC than with a plain image you dd onto a partition: a build pipeline, a content store, deployments, more moving parts. So it's easy to walk away thinking the whole thing must be heavier at runtime too.
It isn't.
OSTree on Torizon sits on top of a perfectly ordinary ext4 filesystem. There's no exotic union or overlay layer translating every read and write at runtime; your application's I/O hits ext4 directly, the same as it would on a traditional image/rootfs (without OSTree). /usr is mounted read-only, while /var and /etc stay writable, and none of that adds a measurable performance penalty for normal file access.
What people are usually reacting to is integration complexity, not runtime I/O. Setting up the build and update flow feels different, so "this is more complex to learn" quietly becomes "this must be slower to run". Those are two very different claims. In practice, boot time and filesystem performance on an OSTree deployment land right where you'd expect them to for the same hardware and the same ext4, because that's exactly what's underneath.
The one place you will notice OSTree doing work is during an update, when it pulls content objects and stages a new deployment. But that happens in the background, on a running system, and it has nothing to do with how fast your application reads a config file at runtime. Furthermore, it is arguably faster than the old A/B partitioning because OSTree just needs to download and write files that actually change between deployments, and even less if static deltas are enabled.
You get two benefits from the smaller updates. The first is bandwidth: a device pulls only the changed objects (and far less with static deltas, as I'll show in Myth 5) instead of a whole image. The second is flash wear: writing only what changed puts far fewer bytes on the eMMC than rewriting a second full partition the way classic A/B does, which adds up over a device’s lifetime.
I checked this on a Verdin iMX8M Plus running Torizon OS, and there's nothing clever going on: /usr is mounted straight on ext4, read-only, with no overlay anywhere in the mount table.
$ findmnt /usr TARGET SOURCE FSTYPE OPTIONS /usr /dev/disk/by-label/otaroot[/ostree/deploy/.../usr] ext4 ro,relatime
A cold read of libstdc++.so.6 right after a reboot, with nothing cached yet, came in around 401 MB/s. That's just what the eMMC on that board does, whether OSTree is in the picture or not. Speeds may vary from distro to distro due to other factors, but that is outside the scope of this test, which only confirms that OSTree itself is not adding overhead.
Myth 2: "OSTree is Unreliable and Prone to Data Corruption"
The worry isn't irrational. Any mechanism that rewrites your root filesystem in the field sounds risky, and we've all seen or heard of a device getting unplugged mid-update and turning into an expensive paperweight. The instinct to distrust OS updates is a good one.
And yet this is arguably where OSTree is at its strongest, not its weakest. OSTree updates are atomic: a new version is staged completely and verified before anything switches over, and then the system flips to it in a single atomic operation. If the power drops while it's pulling or deploying, nothing has changed yet, so you simply boot the deployment you were already running. No half-written rootfs, nothing bricked.
On top of that, OSTree keeps the previous deployment around, so rollback is built in: a bad update can fall back to the last known-good version. Content lives in a checksummed, content-addressed repository, which means corruption is detectable rather than silent. And because /usr is read-only, a stray process or script can't quietly scribble over your OS files in the first place.
A fair question is what happens to the parts that are not read-only: /etc and /var. /etc is better protected than it looks. On an update OSTree never edits your live /etc in place; it builds the new one as a three-way merge of your changes onto the new defaults inside the new deployment directory, and syncs it to disk before the same atomic switch that activates /usr. So a power cut leaves you with either the old /etc or the fully merged new one, never something half-written: the very guarantee /usr gets. The only /etc writes that are plain ext4 are the edits you make by hand on a running system, and those ride on the ext4 journal exactly as they would on any Linux box, where OSTree neither adds protection there nor claims to. /var is where your application's own data lives, and OSTree leaves it untouched across updates, so keeping that consistent is on you, as it would be anywhere.
I wanted more than the theory here, so I went looking for ways to break things on purpose. First, the read-only claim. Without an explicit unlock, even root can't write to /usr; it's the mount that refuses, not file permissions:
# as root $ sudo touch /usr/test touch: cannot touch '/usr/test': Read-only file system
Then corruption. Every file in /usr is stored under a hash of its own contents, so tampering can't stay hidden. When I flipped a few bytes in one object by hand, ostree fsck caught it right away and told me exactly which object and what the checksum should have been:
error: fsck content object b02aeb5b...: Corrupted file object;
checksum expected='b02aeb5b...' actual='53ba4e3a...'
For the power-loss case, I went for the rudest test I could manage: a no-sync reset, the software equivalent of yanking the plug. ext4 replayed its journal on the next boot (EXT4-fs ... recovery complete), and ostree fsck still came back clean across all 17,311 objects, with the booted deployment unchanged.
And the rollback target itself is no special feature; it is simply the previous deployment, still on disk. ostree admin status lists both at once: the one you booted, and the one the system can fall back to.
$ ostree admin status * torizon 7517fc89... Version: 7.6.1+build.38 torizon df20b407... Version: 7.5.0+build.30 (rollback)
That second entry, marked (rollback), is what the Torizon update system points the bootloader back to if a new version fails to come up cleanly. OSTree keeps the known-good deployment around; the update stack decides when to use it.
We've actually written about this myth before, and you can read my previous investigation in the blog post Is Torizon OTA Safe From Power Loss?. If anything, atomic updates plus rollback make OSTree more resilient than any homemade approach, and at least on the same level as other traditional approaches.
Myth 3: "OSTree is Too Complex — I Just Want to Add Files"
This one's fair, and I'll own it. By default /usr is read-only, and this includes paths where an embedded developer might be used to writing things, such as the binaries or libs directories. So if you cp something into it and get "Read-only file system," that's genuinely surprising the first time, especially if you're coming from a setup where the whole rootfs was writable.
The complexity is there for a reason, the same way it is with security. A read-only OS image is exactly what makes updates atomic, rollbacks reliable, and two devices on the same version bit-for-bit identical. Take it away and you lose those guarantees. The goal is not to fight the read-only /usr, but to use the right tool for what you are actually trying to do.
The good news is that there's a clean answer for every situation; it just depends on why you're adding the file.
- Debugging or experimenting on a live device? Use
ostree admin unlock. It gives you a transient writable overlay on top of/usrthat lives in RAM and disappears on reboot, perfect for "let me try something real quick" without permanently changing the OS. - Need a tweak to survive reboots while you iterate?
ostree admin unlock --hotfixmakes the change persistent until the next update overwrites it. Or, alternatively, if you can choose where to put the files, use the home directory/home/torizonor anywhere in the/vardirectory, which are not managed by OSTree. - Putting something into production? Bake it into the image through the build pipeline. TorizonCore Builder handles this for you: you declare your files, configs, and customizations, and its build command rolls them into a custom image in a single step, so they ship as part of a proper, reproducible, updatable deployment.
The transient unlock is the one I reach for most, and OSTree is refreshingly blunt about what it does:
$ sudo ostree admin unlock Development mode enabled. A writable overlayfs is now mounted on /usr. All changes there will be discarded on reboot.
It kept its word: the file I dropped into /usr was gone after a reboot, and /usr was read-only again. The --hotfix variant is the mirror image, since my test file was still there after rebooting. One catch I ran into live, though: a hotfix spends your rollback slot. It marked the running deployment in place and pruned the previous one to make room (Freed objects: 342.1 MB), so it's handy for iterating on a field fix, but it isn't free.
A lot of "I just want to add a file" cases are really application files anyway, and those belong in persistent storage under /var and /home (which lives under /var, by the way), not baked into the OS. The read-only /usr isn't there to annoy you; it's the same property that makes rollback and reproducible deployments work. That's a trade-off, but one that pays off the moment you need to update a fleet safely.
Myth 4: "OSTree Changes the Boot Flow and Is Hard to Understand"
It is different, I'll give you that. There's an initramfs involved, and the bootloader points at a deployment directory rather than a fixed root partition. If your mental model is "bootloader, kernel, mount the root partition," then yes, OSTree rearranges that, and unfamiliar can feel like complicated.
But different isn't the same as magic, and the flow is well documented. It boils down to four steps:
- The bootloader loads the kernel and an initramfs.
- The initramfs reads the active deployment from the kernel command line (the
ostree=argument). - Here's that whole story on a running board. The only OSTree-specific thing on the kernel command line is the
ostree=argument pointing at a deployment:
$ cat /proc/cmdline ... ostree=/ostree/boot.1/torizon/<bootcsum>/0
And ostree admin status is the one-command summary of which deployments exist and which is the fallback:
$ ostree admin status * torizon 7517fc89... Version: 7.6.1+build.38 torizon df20b407... Version: 7.5.0+build.30 (rollback)
I also pulled the initramfs apart to see the pivot for myself. The little hook that reads ostree= and switches root is byte-for-byte the one shipped in the Torizon layer source. No hidden magic, just a documented script.
So the boot flow is a layer of indirection, not a black box. Keep in mind it's documented both in the upstream OSTree docs and in our own knowledge base, and the payoff for that one extra step, the pivot into a deployment, is everything we covered in Myths 1 and 2: atomic updates and clean rollback.
One thing I have glossed over here is Secure Boot. Reworking the boot flow has been quite complex in Torizon OS due to our goal of providing a secure and reliable update system with it. Part of this complexity, although not all of it, comes from OSTree. The upside is that, unless you intend to create your own Linux distro, you get this great feature ready-to-use and abstracted through our tooling. As this is a topic of its own, well beyond OSTree, the secure boot setup is documented separately, and I invite you to read it in the blog about Security Hardening of U-Boot and overall our Secure Boot docs.
Myth 5: "OSTree Doesn't Work for A/B Partitioning Requirements"
There's a real requirement behind this one. Some teams claim they need classic A/B redundancy, sometimes for regulatory (usually an assumption rather than something the standard actually mandates) or internal-standards reasons: two root partitions, update the inactive one, switch over, fall back if it fails. OSTree doesn't lay things out as two fixed rootfs partitions, so on a literal reading, it can look like it doesn't comply.
It does, though, just by a different route. OSTree delivers the same outcome as A/B partitioning: a redundant, fail-safe update with a guaranteed fallback to a known-good system, using deployments instead of two physical partitions. You always keep at least two deployments on a single partition (the current one and the previous one), and the bootloader can fall back to the previous deployment exactly the way an A/B scheme falls back to the other slot. It's functionally A/B; some folks call it "logical A/B."
It even has an advantage. Because deployments share their unchanged files via hardlinks in the content store, you don't pay for two full copies of the rootfs the way a dual-partition layout does. You get the redundancy without doubling the storage. You are even allowed to keep as many deployments as you want, making your system an effective “logical A/B/C/D/…/n” system.
On the device, it really is a single partition, with the rollback living right alongside the current system rather than on a second slot:
$ lsblk NAME SIZE FSTYPE MOUNTPOINT mmcblk2 14.8G `-mmcblk2p1 14.8G /sysroot
And the "two deployments, double the storage" worry doesn't survive contact with the numbers. I measured the combined on-disk size of two deployments for three realistic cases, and the closer the two versions are, the more they share:
| Update | Cost of keeping the second deployment |
|---|---|
| config-only change | 1.00× (basically free) |
| two consecutive nightlies | 1.06× |
| far-apart quarterly release | 1.61× |
| all files change between deployments | 2.00× (worst case, theoretical) |
A literal A/B layout pays 2× every time. OSTree only pays for what actually changed. The same logic carries over to downloads: with static deltas enabled, a nightly update dropped from 37.5 MB to 2.9 MB. Even that 2× worst case (all files changed, which I never hit in practice) is just what a dual-partition layout costs every single time.
So the honest answer depends on how your requirement is written. If it says "the system must support rollback to a known-good image after a failed update," OSTree satisfies that cleanly. If it literally mandates two physical partitions as an implementation detail, that's worth a conversation, and we can help map what the requirement is actually trying to guarantee onto what OSTree provides. The point is this: don't let the word "A/B" rule out a model that gives you the same safety guarantees more efficiently.
Conclusion
If I had to compress all five myths into one sentence, it would be this: OSTree feels unfamiliar, and unfamiliar gets mistaken for slow, fragile, or complicated.
When you actually look at what's running:
- It's plain ext4 underneath, so there's no runtime performance penalty.
- Updates are atomic, with built-in rollback. Power-fail safe by design.
- Adding files is straightforward once you know whether you want
unlock, a hotfix, or a proper build-time customization. - The boot flow is one extra, well-documented step (an initramfs pivot into a deployment), not a black box.
- It gives you A/B-style redundancy and fallback without needing two physical partitions, and for the incremental updates that dominate real fleets, the second deployment is nearly free on disk.
None of this makes OSTree the right tool for every project. There are trade-offs, and I've tried to be upfront about them. But most of the reasons people give for avoiding it don't last long after a look under the hood. And this time I didn't just take my own word for it: every claim above is backed by tests I ran on a Verdin iMX8M Plus, with the full write-up and the steps to reproduce it in Appendix I. If you've been hesitant, try it on real hardware and see for yourself.
One last thing worth saying: on Torizon you do not actually have to become an OSTree expert to get any of this. The integration work is done, so the common cases, such as updating, rolling back, adding files, and customizing an image, just work through the platform tooling, and you can treat OSTree as an implementation detail you never type by hand. This post is for the curious who do want to look under the hood.
And if you run into something that doesn't match what I've described here, or you've got a sixth myth I should add to the list, let me know in the community. I'm looking forward to seeing what you find.
Appendix I — The Tests Behind the Myths
I didn't want this to be another "trust me" post. So before writing it, I put a Verdin iMX8M Plus running Torizon OS 7.6.1 on the bench and tested every myth on real hardware. Each one was run as a small experiment: a clear hypothesis, a null hypothesis that would prove me wrong, a fixed method, and raw output captured straight from the device, including the runs that surprised me. The scripts, raw data, and full per-experiment reports are all publicly available on my GitHub, under leograba/ostree-myths-blog.
The setup
| Property | Value |
|---|---|
| Board | Verdin iMX8M Plus |
| OS | Torizon OS 7.6.1+build.38 (scarthgap) |
| Kernel | 6.6.138-7.6.1 |
| OSTree | 2024.5 |
| Storage | mmcblk2p1 — 14.8 GB ext4, a single rootfs partition |
| Deployments at start | 7.6.1+build.38 (current) + 7.5.0+build.30 (rollback) |
Results at a glance
Each test checks whether OSTree adds a runtime penalty or a hidden layer on the same hardware compared to not using it, not whether Torizon out-benchmarks another distribution. The baseline is a plain non-OSTree image on the identical Verdin iMX8M Plus; since /usr is the same ext4, "no overhead" means the OSTree build performs like the non-OSTree one. Comparing against, say, plain Debian or Android would measure different kernels, schedulers, and userlands — a different question from whether OSTree slows things down.
| Myth | What I tested | Result |
|---|---|---|
| 1 — Slow | ext4 under /usr, no overlay, cold read throughput | Plain ext4, ~401 MB/s, no runtime layer |
| 2 — Unreliable | read-only /usr (even as root), fsck on a tampered object, no-sync power cut, a real rollback | Atomic and self-protecting; rolled back on its own |
| 3 — Too complex | transient unlock, hotfix unlock, /home and /var writes | All three "add a file" paths work as documented |
| 4 — Boot flow | ostree= cmdline, ostree admin status, initramfs hook vs. layer source |
One inspectable extra step; hook byte-identical to source |
| 5 — No A/B | single partition, shared inodes, on-disk cost, download size | Logical A/B; second deployment nearly free for small updates |
Myth 2, in more depth
Reliability got the most attention, because "it might brick" is the scariest objection. Beyond the basics, I ran five harder experiments:
| Experiment | What it showed |
|---|---|
| Hardened write-protection | Even root gets "Read-only file system" on /usr; /etc, /var, and /home stay writable |
| Content-addressing | A deployed /usr file shares an inode with its repo object, and the object's name is its content hash |
| Corruption + fsck | A hand-corrupted object, even one hardlinked into the booted system, is caught with an exact checksum mismatch |
| Power-fail atomicity | A no-sync reset triggers ext4 journal recovery, yet fsck stays clean and the deployment is unchanged |
| Break it, then roll back | With the update client masked, U-Boot's boot counter rolled a broken deployment back after three failed boots |
Myth 5, in more depth
The original test reported a single "~14% saved", which undersold the story. I split it into two independent questions across three real-world cases. First, the on-disk cost of keeping a second deployment:
| Use case | Combined size | vs. 2× | Files shared |
|---|---|---|---|
| config-only change | 1.00× | 50% saved | 99% |
| consecutive nightlies | 1.06× | 47% saved | 99% |
| far-apart quarterly | 1.61× | 19% saved | 70% |
And second, the bytes over the wire, comparing a static delta against pulling every changed object:
| Use case | Object pull | Static delta | Download saved |
|---|---|---|---|
| config-only | 9.8 KB | 6.5 KB | 33% |
| consecutive nightlies | 37.5 MB | 2.9 MB | 92% |
| far-apart quarterly | 152.6 MB | 25.1 MB | 84% |
Two takeaways. A second deployment is nearly free for the incremental updates that dominate real fleets, and static deltas pay off most on mid-to-large updates: a one-line change is already tiny, so there's little for a delta to shave off.
A few honest surprises
ostree admin unlock --hotfixconsumes the rollback slot. It prunes the previous deployment to make room for a clean clone, which is useful but not free.- /var/tmp is volatile (a tmpfs symlink). Persistent data belongs in
/var/lib,/var/rootdirs, or/home. - On this board, the kernel
ostree=argument uses a boot checksum, not the deployment commit, with a symlink bridging the two. Both are on the device and consistent.
How to reproduce this
Everything is scripted and side-effect-free unless a report says otherwise. From a Linux PC on the same network as the device:
git clone https://github.com/leograba/ostree-myths-blog cd ostree-myths-blog/device-tests # baseline + read-only experiments first bash scripts/00-baseline.sh bash scripts/myth4-boot-flow.sh bash scripts/myth2-reliability.sh bash scripts/myth1-performance.sh bash scripts/myth5-ab-partitioning.sh # the unlock tests reboot the device, so run them later bash scripts/myth3-unlock-pre-reboot.sh bash scripts/myth3-unlock-post-reboot.sh
The Python scripts under analysis/ turn the captured raw data into the throughput, storage, and consistency tables above. The reports/ directory has a full write-up per experiment, and reports/summary.md ties them together. A couple of tests change device state on purpose (the hotfix unlock and the rollback rungs) and ship matching cleanup scripts, so check each report's reproducibility section before running those.