system.data.sqlite-binaries: How Duplicati Built Its Own SQLite Supply Chain

A deep look at the repo that cross-compiles native SQLite for the platforms upstream does not fully cover, then packages it into the exact .NET-compatible shape Duplicati needs.

10 min read • View on GitHub • More from duplicati

A precision workshop where one SQLite core is fitted with interchangeable parts and sent into different output crates. The image explains that this repository is not a simple wrapper, but a manufacturing line for native binaries across operating systems and architectures.
Duplicati treats SQLite like a product it has to manufacture, not a dependency it can simply install.

This project contains builds for additional architectures and systems on `System.Data.SQLite.Core` NuGet Package.

Kenneth Christensen, Project Creator/Main Contributor · GitHub - duplicati/system.data.sqlite-binaries
Key Takeaways

Most people think of SQLite in .NET as an API choice. This repo argues the harder problem is elsewhere: native distribution. Duplicati cannot rely on upstream packaging to cover every architecture it cares about, so it manufactures the binaries itself.

That decision turns a quiet infrastructure repo into a strategic asset. It gives Duplicati control over feature flags, build targets, and platform-specific quirks that would otherwise sit outside its reach.

The hidden database factory behind Duplicati

A hedcut-style portrait of Kenneth Christensen based on his GitHub avatar. The portrait identifies the maintainer behind the repository and underscores that this is a project-owned supply chain rather than a generic community package.

The repository exists because Duplicati depends on SQLite as more than storage. Its local database tracks remote backup state, blocks, file hashes, and indexes, which lets the app avoid repeated reads from object storage. That is why the native layer matters so much: if SQLite is unstable or missing on a target platform, the whole backup workflow weakens.

In Duplicati's own framing, SQLite is the local map of the remote world. The repository that ships its binaries is therefore part of the product's core plumbing, not a sidecar dependency.


Why a backup app needs its own SQLite binaries

Duplicati's local database is the memory of the backup set, and the native SQLite layer is what keeps that memory portable.

The important point is not that Duplicati uses SQLite. Plenty of .NET apps do. The important point is that Duplicati needs SQLite to behave consistently across a messy support matrix that includes Linux distributions, macOS, Windows, and multiple CPU families.

That is where project-specific binary ownership becomes valuable. Instead of waiting for a package matrix to line up, Duplicati can ship the exact native surface area it expects.

The repo does not wrap SQLite. It reassembles it

The source tree makes the intent plain. The heart of the project lives under `netfx-src/SQLite.Interop`, where the C interop layer pulls in `sqlite3.c` directly rather than linking against some external shared object. That is the opposite of a thin wrapper. It is a controlled amalgamation.

This matters because it gives the build pipeline ownership over what gets compiled in. Features can be turned on or off at build time, extensions can be bundled, and the final native artifact is shaped to fit the application rather than a generic package.

A close-up of the SQLite interop assembly table. One source sheet is folded into a monolithic binary slab while feature toggles are flipped on around it. The image explains how compile-time choices become part of the shipped database engine.
The binary is not assembled from pieces at runtime. The pieces are baked together before Duplicati ever loads them.

That reassembly strategy is what makes the repo interesting. It is not trying to abstract SQLite away. It is trying to own the exact compilation boundary where SQLite becomes a Duplicati-compatible binary.

#include "../core/sqlite3.c"

#if defined(INTEROP_EXTENSION_FUNCTIONS)
#include "../contrib/extension-functions.c"
#endif

#if defined(_WIN32)
#define INTEROP_EXPORT __declspec(dllexport)
#else
#define INTEROP_EXPORT __attribute__((visibility("default")))
#endif

Interoperability is where the real engineering lives

The portability work is not decorative. `interop.c` has to satisfy .NET expectations on both Windows and Unix-like systems, which means handling export symbols, atomic operations, and the little compatibility edges that let managed code talk cleanly to native code.

That is also why the file carries its own compile-time reporting and defensive platform logic. The repository is not just making a binary. It is documenting, in code, exactly what that binary contains and how it should behave.

#if !defined(_WIN32)
static long InterlockedIncrement(long *value) {
  return __sync_add_and_fetch(value, 1);
}

static long InterlockedDecrement(long *value) {
  return __sync_sub_and_fetch(value, 1);
}
#endif

This is the kind of work that disappears when it succeeds. That invisibility is the point: if the interop layer is doing its job, the rest of Duplicati can pretend SQLite is just there.

How Docker turns one workstation into many build targets

The build scripts turn a single development machine into a small cross-compilation factory. Docker is not just a container here. It is the orchestration layer that lets the project produce native outputs for `386`, `amd64`, `arm/v7`, and `arm64/v8` without asking the developer to own every environment manually.

That matters because native distribution has a long tail of failure modes. A build that works on one host can miss a library, a compiler flag, or an architecture-specific assumption on another. The containerized pipeline reduces that chaos by standardizing the build surface.

LayerWhat it ownsWhat it optimizes forTypical user
System.Data.SQLiteOfficial provider and upstream packagingGeneral ADO.NET compatibilityApps that want the standard package
Microsoft.Data.SqliteModern .NET providerLightweight managed integrationTeams on current .NET stacks
SQLitePCLRawNative loading and raw accessCross-platform abstractionLibraries that want portability
duplicati/system.data.sqlite-binariesProject-specific native buildsExact platform coverage and feature controlDuplicati and its target matrix

This is the repo's strategic niche. It does not compete with the mainstream providers on breadth of audience. It fills the gap where a specific application needs control over native delivery more than it needs another abstraction layer.

The extension pack makes SQLite feel like a different database

One of the more interesting details is how the repository bakes in extension code. Files like `extension-functions.c` add math and string functions, plus aggregates such as `median` and `mode`. That shifts the project from binary packaging toward feature composition.

At that point, the binary is not just SQLite. It is a curated SQLite flavor with a sharper SQL dialect and a deliberately chosen surface area.

#if defined(INTEROP_EXTENSION_FUNCTIONS)
  /* math, string, and aggregate helpers are compiled into the binary */
#endif

That matters because feature choices made at compile time are easier to control than feature choices left to deployment-time dependency resolution. The repo treats the binary as a product boundary.

What this repo is compared with official SQLite providers

The cleanest way to understand the repo is as a division of labor. The official provider is the baseline. `Microsoft.Data.Sqlite` is the modern managed path. `SQLitePCLRaw` is the portability layer. Duplicati's repo is the manufacturing layer that makes the whole stack fit a specific deployment reality.

ProjectNative binary ownershipCross-platform breadthFeature tuningMain advantageMain limitation
System.Data.SQLitePartial, upstream-ledBroad but package-drivenModerateEstablished ADO.NET providerNot every target is packaged the way Duplicati needs
Microsoft.Data.SqliteDelegated through loadingStrong on modern .NETLow to moderateSimple modern APIRelies on other layers for native delivery
SQLitePCLRawManaged over raw native loadingVery broadLowPortable abstractionNot a project-specific build pipeline
duplicati/system.data.sqlite-binariesFully owned by DuplicatiFocused on Duplicati targetsHighExact binary and feature controlNarrow purpose

That distinction is the whole article. This repo exists because Duplicati refuses to treat the native layer as somebody else's problem.


Why this kind of infrastructure repo matters

Infrastructure repos like this rarely get attention because they do not ship a user-facing feature. But they decide whether the user-facing feature works on real hardware, in real packaging environments, across the awkward long tail of architectures that large ecosystems often ignore.

The lesson is broader than SQLite. Modern cross-platform software still depends on old-school control over C code, compiler flags, and binary distribution. The projects that own that layer gain resilience. The ones that outsource it inherit someone else's schedule and someone else's gaps.

Duplicati's SQLite supply chain is a reminder that in software, control over the native layer is often control over the product itself.