Porting BlazeDB to Linux: the bugs weren't where I expected
A few months ago I open-sourced BlazeDB, an embedded Swift database with WAL durability and page-level AES-GCM encryption.
In the launch post, I mentioned that macOS support came together fairly quickly while Linux took much longer. I never really explained why.
This post is that follow-up.
The goal
The target was simple:
- One storage engine
- One Swift package
- Runs on macOS, iOS, and Linux
That meant supporting both GUI applications and server-side/CLI workloads (blazedb doctor, blazedb dump) without Xcode.
The surprising part wasn't Linux itself.
It was discovering how many assumptions I'd accidentally made while developing primarily on Apple platforms.
1. Crypto isn't just import CryptoKit
Apple platforms use CryptoKit.
Linux uses swift-crypto (import Crypto).
The import is straightforward:
#if canImport(CryptoKit)
import CryptoKit
#else
import Crypto
#endif
But encryption isn't a place where portability can be an afterthought.
PBKDF2, page encryption, and WAL frame encryption all pass through the same abstraction layer. Once encryption is part of your storage format, your crypto backend becomes part of your portability surface.
2. Linux exposed a correctness bug, not a Linux bug
This ended up being the most interesting issue.
BlazeDB stores records in a custom binary format inside encrypted pages.
During development I'd assumed loading values directly from raw bytes was safe.
Linux CI immediately proved otherwise.
I added a regression test that intentionally decoded from a misaligned buffer:
Encode record
β
Prepend one byte
β
Decode from subdata(...)
β
Crash
The problem wasn't Linux.
UnsafeRawPointer.load(as:) has the same alignment requirements everywhere.
The difference was the environment.
Unaligned scalar loads often appear to "work" on x86_64 macOS.
Linux CI (and strict ARM) traps immediately.
The fix wasn't platform-specific.
It was removing alignment assumptions entirely by switching to alignment-safe byte reads for decoding and page header parsing.
Linux simply found the bug first.
3. BLAZEDB_LINUX_CORE became its own operating mode
Linux support wasn't just removing SwiftUI imports.
Instead I introduced a dedicated compile-time configuration:
BLAZEDB_LINUX_CORE
The portable core currently includes:
- WAL
- Encryption
openputgetqueryobserve
Some platform-specific features remain outside that configuration, including parts of the query planner, spatial/vector APIs, and trigger persistence.
That means maintaining two build modes.
It's more work, but I'd rather explicitly define the portable surface than pretend every feature works everywhere.
4. Most portability work is boring
None of these issues were individually difficult.
Together they accounted for most of the port.
Examples:
arc4random_bufβUInt8.random(in:)- Different default data directories
- SwiftPM compiling every test target even when filtering one suite
Portability usually isn't one giant blocker.
It's dozens of tiny ones.
5. Cheap Linux CI caught the expensive bug
The alignment issue wasn't discovered by chaos testing.
It was caught because Linux Tier 0 runs on every pull request.
Current test layout:
| Stage | Runs |
|---|---|
| Pull Requests | macOS Tier0 + Tier1, Linux Tier0 |
| Nightly | Linux Tier1 + Tier2, macOS Tier2 + TSan |
| Weekly | Tier3 destructive / chaos testing |
Linux isn't a promotion pipeline.
It's another source of signal.
Sometimes the cheapest tests find the most valuable bugs.
Current status
Verified today
- BlazeDBCore builds on Linux
- Tier0 passes on Ubuntu 22.04 (Swift 6.2+)
- CLI targets build
- Portable storage path exercised in CI
Still true
- Linux has seen much less production use than macOS.
- Most confidence comes from CI rather than deployment volume.
- Feature parity isn't the goal yet. A portable, tested storage engine is.
Questions
For those maintaining Swift packages across Apple platforms and Linux:
- Did you split into portable/core targets, or stay with one target and lots of
#ifs? - How do you validate binary layouts without exploding your CI matrix?
- Any good patterns for keeping
CryptoKitandswift-cryptoabstractions aligned over time?
I'd be interested to hear how others have approached long-term cross-platform maintenance for storage engines and binary formats.