# bbolt: an embedded key/value store for Go programs that do not need a database server

> bbolt is a pure Go fork of Bolt with a fixed file format and a small API. It suits single-process Go services that need durable local storage, and it is the wrong choice the moment a second process needs to write.

**etcd-io/bbolt** — An embedded key/value database for Go.

- Repository: https://github.com/etcd-io/bbolt
- Website: https://go.etcd.io/bbolt
- Stars: 9,765 · Forks: 758
- Language: Go
- License: MIT
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/etcd-io-bbolt

## What bbolt is for, and who should reach for it

bbolt is an embedded key/value database written in Go. It is a fork of Ben Johnson's Bolt, and the README states the fork's purpose plainly: to give the Go community an active maintenance and development target for Bolt, with improved reliability and stability, while preserving backwards compatibility with the Bolt API. The README also notes that Bolt itself was inspired by Howard Chu's LMDB.

The audience is narrow and specific. The README says the goal is a simple, fast and reliable database for projects that do not require a full database server such as Postgres or MySQL. That means a Go binary that ships with its own storage: a CLI tool keeping state on disk, an agent caching results between runs, an application where the data fits on one machine and the process that writes it is the process that reads it. The README describes Bolt as a low-level piece of functionality, and says the API will be small and focus on getting values and setting values.

If you are looking for a server to connect to over a network, this is not it. There is no wire protocol, no port, no daemon. The database is a file your program opens, and the README describes the top-level DB object as a single file on disk representing a consistent snapshot of your data.

## Transactions, buckets and the single-writer rule

The concurrency model is the part that decides whether bbolt fits your program. The README states that Bolt allows only one read-write transaction at a time but allows as many read-only transactions as you want, and that each transaction has a consistent view of the data as it existed when the transaction started.

Writes go through DB.Update, which takes a closure. Returning nil commits; returning an error rolls back. Reads go through DB.View. The README warns that individual transactions and the objects created from them, such as buckets and keys, are not thread safe, and that creating a transaction from the DB is thread safe. So the DB handle can be shared across goroutines, but a transaction cannot.

There is a second constraint that catches people out. The README says transactions should not depend on one another and generally should not be opened simultaneously in the same goroutine, because the read-write transaction needs to periodically re-map the data file and cannot do so while any read-only transaction is open. Even a nested read-only transaction can deadlock, since the child can block the parent from releasing its resources. That is a design consequence of memory mapping, not a bug, and it shapes how you structure code around the library.

Data lives in buckets, which the README treats as the unit for keys and values, with nested buckets supported. Cursors provide iteration, including prefix scans, range scans and ForEach. There is also an autoincrementing integer sequence for a bucket, which is the usual way to get monotonic IDs without a separate counter store.

## Installing bbolt and writing your first bucket

The README gives the install path directly. With Go installed, go get retrieves the library and updates your go.mod and go.sum files.

```bash
go get go.etcd.io/bbolt@latest
```

If you want the command line utility rather than the library, the README offers two options: run it without installing, or install it into $GOBIN, which defaults to $GOPATH/bin or $HOME/go/bin when GOPATH is unset.

```bash
go install go.etcd.io/bbolt/cmd/bbolt@latest
```

The README's importing example uses the import alias bolt and calls bolt.Open with a path, a file mode of 0600, and a nil options pointer. The database file is created if it does not exist, and the deferred Close releases it.

```go
import bolt "go.etcd.io/bbolt"

db, err := bolt.Open(path, 0600, nil)
if err != nil {
  return err
}
defer db.Close()
```

For a first real write, open the file, run DB.Update, create a bucket, and put a key. The README's transaction example shows the closure returning nil to commit and returning an error to roll back, and it advises always checking the returned error because it reports disk failures that can stop a transaction from completing.

```go
err := db.Update(func(tx *bolt.Tx) error {
  b, err := tx.CreateBucketIfNotExists([]byte("config"))
  if err != nil {
    return err
  }
  return b.Put([]byte("region"), []byte("eu-west"))
})
```

One practical detail from the README worth applying on day one: because Bolt takes a file lock on the data file, opening a database that another process already holds causes the call to hang until that process closes it. Passing a timeout to Open turns an indefinite wait into an error you can handle.

```go
db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second})
```

## Where bbolt breaks down: multi-process access and long read transactions

The file lock is the sharpest limitation. The README states that multiple processes cannot open the same database at the same time, and that opening an already open database causes it to hang until the other process closes it. If your architecture assumes a writer process and a separate reader process, bbolt is the wrong tool. The timeout option makes the failure visible, but it does not make concurrent multi-process access work.

The single read-write transaction limit is the second boundary. A workload that needs many concurrent writers will serialize on that one transaction, and the README does not offer a sharding or partitioning mechanism to work around it. Splitting data across several files is something an application can do, but the README does not present it as a supported pattern.

Long-lived read transactions interact badly with writes. Because the read-write transaction must re-map the file and cannot do so while a read-only transaction is open, holding a read transaction open while writes proceed is the kind of pattern the README's deadlock warning points at. The same warning covers nested read-only transactions in one goroutine.

The README's own comparison section frames the trade-off against alternatives. Against Postgres and MySQL, the difference is the absence of a server. Against LevelDB and RocksDB, the README draws the distinction around the log-structured merge-tree design those use. Against LMDB, the README compares two memory-mapped stores with different provenance and maintenance. None of these comparisons makes bbolt faster or slower in absolute terms; the README does not publish benchmark numbers, and neither should you assume any.

## Choosing between bbolt and a log-structured store such as BadgerDB

The honest alternative in the Go ecosystem is a log-structured key/value store, and BadgerDB is the one people most often weigh against bbolt. The difference is structural rather than cosmetic. bbolt is a B+tree over a memory-mapped file with a single read-write transaction; a log-structured store appends writes and merges them in the background, which changes both write throughput characteristics and disk usage behaviour.

That difference matters for read patterns. A copy-on-write B+tree like bbolt gives you a consistent snapshot for the life of a read transaction, which is exactly what the README describes. Log-structured designs trade some of that simplicity for write concurrency and space amplification behaviour that a B+tree does not have. If your workload is write-heavy with many concurrent writers, bbolt's single-writer rule is the constraint you will hit first, and a log-structured store is the direction to look.

If your workload is read-heavy, fits on one machine, and you value a small API and a fixed file format, bbolt's constraints cost you little. The README states that Bolt is stable, the API is fixed, and the file format is fixed. A fixed format is a real operational property: it means the file you write today is the file you read later, and it is why the README can describe backwards compatibility with the Bolt API as a goal of the fork rather than an aspiration.

Other names appear in the related searches around this project, including boltBrowser and NutsDB, but the README does not discuss them, so there is nothing here to compare beyond the alternatives the README itself names.

## Maintenance, releases and the MIT licence

The repository is not archived, and the last push was on 2026-09-15. Recent releases listed for the project are v1.5.0 on 2026-06-21, v1.4.3 on 2025-08-19, and v1.3.12 on 2025-08-19. The README describes the fork's purpose as providing an active maintenance and development target for Bolt, and that framing is consistent with the release history.

Versioning is documented. The README states that bbolt uses semantic versioning, that the API should not change between patch and minor releases, and that new minor versions may add features to the API. That is a useful upgrade contract: patch and minor bumps should not break your call sites, but a minor bump can add surface area you may want to review.

The go.mod file requires Go 1.26.0 and pins a toolchain of go1.26.8, with direct dependencies on cobra, pflag, testify, gofail, golang.org/x/sync and golang.org/x/sys. The Makefile defines fmt, lint, test and coverage targets, and the test target runs the suite twice, once with TEST_FREELIST_TYPE=hashmap and once with array, with BBOLT_VERIFY=all set. If you vendor or pin bbolt, that Go version floor is the thing to check against your own toolchain first.

The licence is MIT. That is permissive and places few conditions on redistribution, but it is your responsibility to confirm how it interacts with your own distribution model. Nothing here is legal advice.

Upgrade cost is dominated by the file format rather than the API. The README describes the format as fixed, and the fork's stated goal is backwards compatibility with the Bolt API. A fixed format means an upgrade is a library swap rather than a migration, which is the main reason the maintenance burden for a bbolt dependency is low. The counterweight is that a fixed format also means format-level improvements are not something you should expect from a version bump.

## Conclusion

Adopt bbolt when one Go process owns the data file and you want transactions without running a server. Do not adopt it if several processes must write the same file, since Bolt takes a file lock and a second opener hangs until the first closes. Before committing, verify that your access pattern fits a single read-write transaction at a time, and test the timeout option on bolt.Open so a contended lock fails fast instead of blocking.

## FAQ

### What is bbolt and how does it work?

bbolt is an embedded key/value database for Go, forked from Bolt, that stores data in a single memory-mapped file your program opens directly. Transactions go through DB.Update for writes and DB.View for reads, with one read-write transaction allowed at a time and any number of read-only transactions.

### How do I install the bbolt CLI?

The README gives go install go.etcd.io/bbolt/cmd/bbolt@latest, which places the bbolt command in $GOBIN, defaulting to $GOPATH/bin or $HOME/go/bin when GOPATH is unset. You can also run it without installing via go run go.etcd.io/bbolt/cmd/bbolt@latest.

### Can two processes open the same bbolt database at the same time?

No. The README states that Bolt obtains a file lock on the data file, so multiple processes cannot open the same database at the same time, and a second opener hangs until the first process closes it. Passing a Timeout option to bolt.Open makes that wait fail with an error instead.

### Why does a bbolt read transaction deadlock with a write?

The README explains that the read-write transaction needs to periodically re-map the data file and cannot do so while any read-only transaction is open. It adds that transactions should not depend on one another or be opened simultaneously in the same goroutine, since even a nested read-only transaction can block its parent from releasing resources.

### What licence does bbolt use?

The project is licensed under MIT. That is permissive, but you should confirm how it fits your own distribution model; nothing in the README addresses licensing obligations.

## Sources

- [etcd-io/bbolt on GitHub](https://github.com/etcd-io/bbolt)
- [License: MIT](https://github.com/etcd-io/bbolt/blob/main/LICENSE)
- [Project website](https://go.etcd.io/bbolt)
- [README](https://github.com/etcd-io/bbolt/blob/main/README.md)
- [Releases](https://github.com/etcd-io/bbolt/releases)

---

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