bbolt is used as an embedded persistent key/value datastore for task state. Instead of running a separate database server or making network calls for reads and writes, a Go process opens a local database file and accesses data through the library. This fits an orchestrator whose manager and workers need simple, fast storage without the operational weight of a full database server. section 1.7 section 11.8
The core mental model is the same as an in-memory Go map: a task UUID is a key and the task is its value. Persistence changes the implementation, not the store-facing role. A TaskStore still satisfies the Store interface with Put, Get, List, and Count; it replaces the map with a *bolt.DB, a database-file name, file permissions, and a bucket name. section 11.8 section 11.9
A persistent task store is assembled in small, explicit steps:
- Open a database file with bolt.Open, passing its path and file mode; report an error if it cannot be opened. section 11.9
- Record the database handle, file metadata, and bucket name in TaskStore, then create the bucket or reuse it when it already exists. section 11.9
- Use an Update transaction to modify a bucket. Put serializes a task as JSON and stores the resulting bytes under the task key. section 11.9
- Use a View transaction to read a bucket. Get loads and unmarshals one task; List iterates entries and unmarshals each value; Count iterates entries to total them. section 11.9
- Close the database handle when the store is no longer needed, because the datastore writes to a file on disk. section 11.9
Persisting tasks behind one interface
A task store can be initialized with a file, permissions, and a bucket such as tasks. Calling Put with a task UUID marshals the task to JSON and writes it into that bucket. A later Get reads the bytes for the same UUID and unmarshals them into a task. Code using Store can retain the same Put, Get, List, and Count operations used by the in-memory implementation while gaining durable state. section 11.9
Listing 11.22 The NewTaskStore helper function
func NewTaskStore(file string, mode os.FileMode, bucket string)
(*TaskStore, error) {
db, err := bolt.Open(file, mode, nil) #1
if err != nil {
return nil, fmt.Errorf("unable to open %v", file)
}
t := TaskStore{ #2
DbFile: file,
FileMode: mode,
Db: db,
Bucket: bucket,
}
err = t.CreateBucket() #3
if err != nil {
log.Printf("bucket already exists, will use it instead
of creating new one")
}
return &t, nil #4
}
#1 Creates a datastore by calling the BoltDB library’s Open function, passing in the file, mode, and bucket from our function signature, and checks for any errors
#2 Creates an instance of the TaskStore and assigns it to the variable t
#3 Creates the bucket by calling the CreateBucket method and checks for any errors
#4 Returns a pointer to the newly created task store
The initializer makes the persistence boundary visible: open the file, retain the bolt.DB handle and configuration, ensure a bucket exists, and return the ready-to-use store. section 11.9
This design deliberately keeps the database role narrow. It stores orchestrator tasks in a key/value model rather than modeling data as relational tables and columns. The result is a practical persistence layer for a focused Docker-container orchestrator, not an attempt to build a replacement for a full database server or production-scale orchestration platform. section 1.7 section 11.8 section 13.4
