One of the reasons I fell in love with Go all those years ago was the idea that I could distribute a single binary that only contained my application, but also the supporting infrastructure needed to run that binary without any tooling installed. Coming from Ruby, a language where you primarily distribute an entire folder structure that requires runtime tooling, this was revolutionary for me, and immediately my mind buzzed with the possibilities.

Several years ago I was building an enterprise web product for a client. This project, as I’ve talked about before, eventually evolved in Buffalo. One of the requirements for Buffalo was to keep the promise of a single binary and this meant embedding static files in Go binaries.

As I searched around I found several tools that did just this. The one that caught my eye was go.Rice. What I liked most about it was that in would fall back to the file system during development meaning you didn’t have to keep re-embedding the files every time you made a change. Early versions of Buffalo shipped with go.Rice as its embedding tooling.

As early adopters started deploying the first Buffalo applications in production, bug reports started coming in with very issues regarding go.Rice. It appeared that, for a variety of reasons, we needed a new solution.

It was that Packr came to be. It adopted the go.Rice API for easy migration of tools. For the past few years Packr has played a large role in Buffalo’s increased popularity by helping to keep that one binary promise.

Unfortunately, Packr’s API and underlying architecture have proven to be both problematic and lossy. Important meta data such as mod time, mode, size, etc… are all lost when using Packr. This makes for a variety of bugs, but also limits what developers can do when using Packr instead of the standard library’s OS package to work with files. It was time to rethink the problem.