# TigerBeetle: a financial transactions database written in Zig

> TigerBeetle starts from a narrow premise, that a ledger is not a table of balances but a list of movements, and everything else in the design follows from that. This is a look at the debit and credit primitives, the single binary deployment and the repl session that makes the model inspectable.

**tigerbeetle/tigerbeetle** — The financial transactions database designed for mission critical safety and performance.

- Repository: https://github.com/tigerbeetle/tigerbeetle
- Website: https://tigerbeetle.com
- Stars: 17,171 · Forks: 917
- Language: Zig
- License: Apache-2.0
- Published: 2026-10-08 · Updated: 2026-10-08 · Language: en
- Canonical page: https://hysenlabs.com/projects/tigerbeetle-tigerbeetle

## A database with a job description rather than a feature list

Most database README files open with a compatibility table or a list of query languages. This one opens with a sentence that states a design constraint: TigerBeetle is the financial transactions database designed for mission critical safety and performance to power the next 30 years of OLTP.

The scope is narrow in a way that changes the engineering. It is written in Zig, released under Apache-2.0, hosted at tigerbeetle.com, and its documentation lives at docs.tigerbeetle.com rather than in the repository. The repository tree is correspondingly small at the top level: a build.zig build script, a src directory, a docs directory, a CHANGELOG and a README. There is no examples directory and no client library directory, which tells you the intended integration path is the network protocol and language clients living elsewhere.

Activity on the branch is current rather than historical. The main branch was last pushed on 2026-08-31, and three releases are recent: 0.17.9 on 2026-07-06, 0.17.8 on 2026-06-22 and a prerelease 0.17.7-1 on 2026-06-21. The version numbers sit in the zero-series range, which for a database that promises thirty years is worth noticing. A zero-series component is still allowed to change interfaces in ways a one-series component is not.

The README also links to a design methodology document, TIGER_STYLE.md under docs, alongside conference talks on redesigning OLTP. That pairing of engineering method with storage engine and consensus protocol discussion is the clearest signal of what kind of project this is: it is being built as a system with a stated correctness argument, not as a query engine with a performance chart.

## Four commands to a running single-replica cluster

The quickstart is unusually short, and the shape of it matters more than the contents. A single-replica cluster is the development default, and the sequence is download, check version, format, start:

```console
$ curl -Lo tigerbeetle.zip https://linux.tigerbeetle.com && unzip tigerbeetle.zip
$ ./tigerbeetle version
$ ./tigerbeetle format --cluster=0 --replica=0 --replica-count=1 --development 0_0.tigerbeetle
$ ./tigerbeetle start --addresses=3000 --development 0_0.tigerbeetle
```

There is no package manager, no configuration file and no service unit. You fetch an archive from a host-specific URL, unzip it, and you have a binary. The README notes this is a single-replica cluster on Linux and links to a start page for other platforms, which means the Linux path is the well-trodden one and everything else is a supported variation rather than the default.

The filename convention in that start command is worth reading twice. `0_0.tigerbeetle` encodes cluster zero and replica zero, and the same file is passed to both `format` and `start`. Formatting is therefore an explicit, separate, destructive operation that you perform once, not an implicit thing that happens on first boot. The `--development` flag appears on both subcommands and changes the guarantees the process makes about durability, so the difference between a development cluster and a real one is a flag you must remember to remove.

The addresses flag takes port 3000. That default will collide with anything else on the machine, which is the first small friction point you will hit.

## Debit and credit primitives instead of a balances table

The data model is the argument. Instead of a table of accounts with a balance column that application code updates, TigerBeetle models accounts and transfers, where a transfer moves an amount from a debit account to a credit account within a ledger under a code.

Look at the terminology closely, because it is doing real work. The balance of an account is not stored as a number you read and trust. It is derived from the sum of the transfers that touch that account, tracked as debits posted, debits pending, credits posted and credits pending. The split between pending and posted is the interesting half. It gives you a way to hold a reservation that has not yet become a final movement, which is what authorisation is, and it is exactly the thing most hand-rolled ledgers get wrong under concurrent writes.

The read side exposes this through `lookup_accounts`, which returns the derived counters rather than a single balance field. That means the read model is honest about how the number was produced. An application can no longer accidentally believe a balance is authoritative when it was written by a path that skipped a transfer.

Every operation also carries a timestamp in its response, and every account carries a user data field and a flags field. The flags field is the extension point: it is empty in the example session, which tells you the core model does not require you to encode policy into the schema to get started.

The design decisions behind this are laid out in a video introduction linked from the README, specifically about the debit and credit primitives, and in a QCon SF talk on redesigning OLTP. If the primitive choice is the part you are evaluating rather than accepting, those two links are where the argument is made properly.

## The repl session makes the invariants visible

The second console fence in the README is not installation. It is a live session against the running cluster, and it is the fastest way to understand the data model.

You start `./tigerbeetle repl --cluster=0 --addresses=3000` and get a prompt rather than a SQL dialect. You create two accounts with `create_accounts`, and each statement returns a JSON object carrying a nanosecond timestamp and a status string of the form `tigerbeetle.CreateAccountStatus.created`. You then create a transfer of amount 10 from account one to account two, and finally you call `lookup_accounts` for both.

That final read is where the model explains itself. Account one comes back with debits posted equal to 10 and every other counter at zero. Account two comes back with credits posted equal to 10 and everything else zero. Ten units moved, and the only evidence is the pair of derived counters. There is no row anywhere that says balance equals 10, because there is no such row.

The namespace prefix in the status strings is a small design note worth noting. Requests name the operation they invoke, `create_accounts`, `create_transfers`, `lookup_accounts`, and responses are namespaced by the operation that produced them. Errors would presumably follow the same pattern, which means a client can branch on a precise failure mode rather than on a generic error code.

The repl is also where you would feel the latency character of the system. These are accounting operations, not analytic queries, and the timestamp in each response is the granularity at which ordering decisions are made.

## A single static binary and a Zig build file

Deployment is the part that will decide whether this fits your operations, and the answer is unusually clean.

There is one binary. There is no external service to install alongside it, no message broker, no cache tier. `format` writes the data file, `start` reads it, and that is the whole lifecycle of a replica. For a ledger, that matters more than it would for a read-heavy store, because the system of record is the one thing you want to have the fewest ways to misconfigure.

The build is driven by `build.zig`, which means anyone with a Zig toolchain can produce the binary themselves. That is a real portability property rather than a philosophical one. A team that cannot accept a closed binary distribution still has a path, and the Apache-2.0 licence removes the question of whether a commercial deployment is permitted.

Against that, the zero-series version numbers deserve a second look. 0.17.9 tells you the project has iterated a great deal, and it also tells you the interface contract may still move between minor versions. If you adopt this, pin the exact binary version rather than tracking the branch.

The contrast with a general-purpose relational database is stark in this area. Postgres wants a server process, a data directory, a connection protocol and usually a replication topology. TigerBeetle wants an archive and a file. Teams that have spent years operating the former will find the latter almost suspiciously simple, and the right question is whether that simplicity comes with something they were implicitly paying the Postgres team to handle.

## Where TigerBeetle sits against Postgres

The comparison that shows up most often in search results for this project is against Postgres, and it is worth being precise about where each one wins.

Postgres is a general-purpose relational system with an enormous ecosystem, an unmatched set of extensions and a data model that will accept whatever shape your application has. It will hold a ledger table perfectly happily. What it will not do is stop two concurrent transactions from computing the same balance and both committing, unless you have written the locking, the isolation level and the retry loop correctly. That work is real, it is per-project, and it is the work TigerBeetle exists to remove.

TigerBeetle's side is that the correctness argument is written once, in Zig, and you inherit it. Its debit and credit primitives plus pending versus posted accounting cover the failure modes that financial applications get wrong. What you give up is everything else: no general queries, no arbitrary schema, no reporting joins, no ad hoc analysis.

There is also a Rust angle in the search results that is worth flagging, because TigerBeetle is not written in Rust even though Rust is common in this space. The clients live in their own repositories, so if Rust is your integration language the answer is to fetch a client rather than to expect one in this tree.

The practical conclusion is that TigerBeetle competes on a narrow axis. If you are building a payments ledger, a marketplace balance system or an exchange position tracker where a double-entry invariant is the core correctness property, the specialised model is worth learning. If you are choosing a database in general, this is not the general answer, and the fact that it runs as one static binary will not change that.

## Conclusion

TigerBeetle earns a place in your stack if your real problem is double-entry correctness under concurrency, which is exactly the class of bug a general-purpose relational database asks you to enforce in application code. It is the wrong tool if your workload is wide-column, loosely structured and read-heavy, because the rigid account and transfer model has nowhere to put that. Before anything else, run the four commands above on Linux and confirm your build pipeline can ship a static binary, then read the accounting documentation closely enough to decide whether your ledger needs pending versus posted balances. If it does, prototype a real transfer loop against the repl session rather than a toy one.

## FAQ

### What type of database is TigerBeetle?

TigerBeetle is a financial transactions database, a specialised OLTP system written in Zig and designed around double-entry accounting. It stores accounts and transfers rather than general rows, tracks debits and credits in posted and pending states, and runs as a single static binary that you format once and start.

### How much does TigerBeetle cost to run?

The source is licensed under Apache-2.0 and the quickstart downloads a binary directly, so there is no proprietary licence fee in the repository. Your actual cost is operational: the hardware and the engineers who run the replicas. Open source licences do not tell you what a distributed database costs at your scale, so size that separately from the software.

### Does TigerBeetle replace Postgres for a general application?

Not for general work. TigerBeetle models accounts, transfers, ledgers and codes, so wide, loosely structured data has no obvious home in it. It replaces Postgres when double-entry correctness under concurrency is the property you cannot afford to implement yourself, and it does not replace Postgres when you need arbitrary queries, extensions and ad hoc reporting.

## Sources

- [License: Apache-2.0](https://github.com/tigerbeetle/tigerbeetle/blob/main/LICENSE)
- [Project website](https://tigerbeetle.com)
- [README](https://github.com/tigerbeetle/tigerbeetle/blob/main/README.md)
- [Releases](https://github.com/tigerbeetle/tigerbeetle/releases)
- [tigerbeetle/tigerbeetle on GitHub](https://github.com/tigerbeetle/tigerbeetle)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tigerbeetle-tigerbeetle
