bbolt: Single-File Embedded Key/Value Storage for Go, With One Writer, One File Lock and a Fixed Format
An embedded key/value database for Go.
At a glance
- What is it?
- bbolt is the etcd-io fork of Bolt, an embedded key/value database for Go that lives in one file and gives you transactions instead of a query language. It is fast to adopt and strict about concurrency: one writer, one process per file.
- Who is it for?
- bbolt fits Go programs that need durable local storage with transactions and no separate database server. It does not fit workloads with several writers, several processes on one file, or a need for queries beyond keys and ranges.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- etcd-io/bbolt9,831 ★ · +84
| Repository | 2026-09-22 | 2026-11-14 |
|---|---|---|
| etcd-io/bbolt | 9.7k | 9.8k |
84 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
A fork whose job is to keep Bolt alive without breaking the Bolt API
bbolt is a fork of Ben Johnson's Bolt key/value store. The stated purpose of the fork is to give the Go community an actively maintained and developed target for Bolt, with the goal of improved reliability and stability. It includes bug fixes, performance enhancements and features not found in Bolt, while preserving backwards compatibility with the Bolt API.
That compatibility is why the import line in the examples still aliases the package as bolt. Code written against Bolt keeps compiling, and the aliasing means call sites read the same as before. The project describes Bolt as a pure Go key/value store inspired by Howard Chu's LMDB, meant for projects that do not need a full database server such as Postgres or MySQL. The release history shows what the maintenance looks like: v1.5.0 on 2026-06-21, and v1.4.3 and v1.3.12 both on 2025-08-19, which indicates fixes landing on more than one release line. The last push was on 2026-10-16.
The API is deliberately tiny, so anything beyond getting and setting values is your code
The README says the API will be small and only focus on getting values and setting values. That is the design position, not a gap to be filled later. There is no query language and no network protocol. You get a DB, transactions, buckets, key/value pairs, cursors for iterating, and nested buckets.
The consequence is that secondary indexes, joins and filtering by value are yours to build. If you store a record under one key and want to look it up by another field, you maintain a second bucket yourself inside the same transaction. The table of contents lists prefix scans and range scans, an autoincrementing integer for a bucket, and a ForEach helper, which are the tools you assemble such structures from. Teams that expect a SQL engine will find the surface area too thin, and that thinness is the trade the project offers for simplicity.
One open file means one process, and a second opener hangs unless you set a timeout
A bbolt database is a single file on disk, and the DB object represents a consistent snapshot of the data. Opening it takes a path, a file mode and options:
db, err := bolt.Open("my.db", 0600, nil)The library takes a file lock on the data file, so multiple processes cannot open the same database at the same time. A second opener does not fail; it hangs until the first process closes the file. To avoid an indefinite wait you pass a timeout option:
db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second})In practice this rules out a design where a web server and a separate maintenance script both open the same file. Either the script talks to the server, or it runs while the server is stopped. The data file is created if it does not exist, in the current directory when you use a relative name.
Only one read-write transaction runs at a time, while readers are unlimited
Bolt allows only one read-write transaction at a time but any number of read-only ones. Each transaction sees the data as it existed when it started. A read-write transaction is started with Update, and returning nil commits it:
err := db.Update(func(tx *bolt.Tx) error {
...
return nil
})Returning an error anywhere in the closure rolls back. The README insists that you always check the returned error, because it reports disk failures that can leave a transaction incomplete. A read-only transaction uses View instead, and inside it you can retrieve buckets and values and copy the database, but no mutating operation is allowed.
The cost is write throughput that is bounded by serialization: two goroutines that both want to write queue behind each other. Each Update also waits for the disk to commit its write, so many tiny updates are slow. The table of contents lists batch read-write transactions as the feature that addresses this.
Transactions and the objects inside them are not thread safe, and nesting can deadlock
Creating a transaction from the DB is thread safe. The transaction itself, and everything created from it such as buckets and keys, is not. To work with data in several goroutines you start a transaction for each one, or you use locking so that only one goroutine touches a transaction at a time.
There is a sharper warning about opening transactions inside one another. Transactions should not depend on one another and generally should not be opened simultaneously in the same goroutine. The read-write transaction periodically needs to re-map the data file, and it cannot do that while any read-only transaction is open. Even a nested read-only transaction can deadlock, because the child can block the parent from releasing its resources. So a helper that opens its own View while you are already inside an Update or View is a bug waiting for the wrong timing. Pass the transaction down instead of the DB.
New releases need a Go 1.26 toolchain, and the test matrix covers two freelist types
The go.mod declares go 1.26.0 with toolchain go1.26.9, so the current code asks for a very recent Go. Projects pinned to an older compiler have to stay on an older bbolt release line, which is presumably why 1.4.x and 1.3.x releases were cut on the same day in 2025. The command line tool pulls in cobra and pflag, plus golang.org/x/sys for platform calls.
The top-level files show where platform differences sit: bolt_aix.go, bolt_android.go, bolt_linux.go, bolt_openbsd.go, bolt_solaris.go, bolt_unix.go and bolt_windows.go, along with separate mlock files for unix and Windows. The Makefile runs the whole test suite twice, once with TEST_FREELIST_TYPE set to hashmap and once to array, always with BBOLT_VERIFY=all, and exposes ENABLE_RACE, CPU and TIMEOUT knobs. Race detection is off unless you ask for it, and the default timeout is 30 minutes.
Install the library with go get, and the bbolt command with go install
Adding the library to a module is one command, which updates go.mod and go.sum:
$ go get go.etcd.io/bbolt@latestThe command line utility has two routes. You can run it without installing it:
$ go run go.etcd.io/bbolt/cmd/bbolt@latestOr you can put it on your path with go install, which writes into your GOBIN, defaulting to GOPATH/bin or $HOME/go/bin when GOPATH is not set:
$ go install go.etcd.io/bbolt/cmd/bbolt@latestBecause the examples use @latest, you receive the newest tag and with it the Go 1.26 requirement. Pin a specific version if your build image has an older compiler. The README also lists a Mobile Use section for iOS and Android, and a Caveats and Limitations section, both worth reading before you commit a design to this store. The project states that it uses semantic versioning, so patch and minor releases should not change the API, though minor versions may add to it.
Editorial conclusion
bbolt fits Go programs that need durable local storage with transactions and no separate database server. It does not fit workloads with several writers, several processes on one file, or a need for queries beyond keys and ranges. Check the Go version in go.mod and the Caveats section of the README before choosing it.
Frequently asked questions
What is Bolt and how does it work?
Bolt is a pure Go key/value store inspired by LMDB, and bbolt is its maintained fork. A database is a single file, accessed through transactions with one writer at a time.
Can two processes open the same bbolt database?
No. bbolt takes a file lock, so a second process hangs until the first closes the database. Passing a Timeout in bolt.Options stops the wait from being indefinite.
Does bbolt support several simultaneous writers?
No. It allows only one read-write transaction at a time, though any number of read-only transactions can run alongside it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/etcd-io-bbolt)Star history chart for your README
[](https://hysenlabs.com/projects/etcd-io-bbolt)Was this useful?