Compress and embed static files and assets into Go binaries and access them with a virtual file system in production
Go
205
25 commits
updated Jun 28, 2026
stuffbin is a utility + package to compress and embed static files and assets into Go binaries for distribution. It supports falling back to the local file system when no embedded assets are available, for instance, in development mode. stuffbin is inspired by zgok but is simpler and has better abstractions.
Update (June 2026): stuffbin seems to be feature complete with no changes required in ~3 years. It is actively used in critical production systems.
Go 1.16 introduced the //go:embed directive that allows embedding of files into Go binaries without any external utilities. stuffbin offers a few key advantages over native embedding in its current form.
/home/local/path/file.txt as /app/file.txt).//go:embed directives.../../path).
Go embed does not permit embedding of files outside of a .go file's directory. This makes it difficult for programs structured in a cmd directory to embed files outside it.
stuffbin compresses and embeds arbitrary files to the end of Go binaries. This does not affect the normal execution of the binary as the compressed data that is appended beyond the binary's original size is simply ignored by the operating system. When a stuffed application is executed, stuffbin reads the compressed bytes from self (the executable), uncompresses the files on the fly into an in-memory filesystem, and provides a FileSystem interface to access them. This enables Go applications that have external file dependencies to be shipped a single fat binary, commonly, web applications that have static file and template dependencies.
go install github.com/knadh/stuffbin/...@latest
For macOS/Linux users, you can install via brew
$ brew install stuffbin
# -a, -in, and -out params followed by the paths of files to embed.
# To normalize paths, aliases can be suffixed with a colon.
stuffbin -a stuff -in /path/to/exe -out /path/to/new.exe \
static/file1.css static/file2.pdf /somewhere/else/file3.txt:/static/file3.txt
stuffbin -a id -in /path/to/new/exe
stuffbin -a unstuff -in /path/to/new/exe -out assets.zip
To test this, cd into ./mock and run go run mock.go
package main
import (
"fmt"
"log"
"net/http"
"os"
"github.com/knadh/stuffbin"
)
func main() {
// Get self executable path.
path, err := os.Executable()
if err != nil {
log.Fatalf("error getting executable path: %v", err)
}
// Read stuffed data from self.
fs, err := stuffbin.UnStuff(path)
if err != nil {
// Binary is unstuffed or is running in dev mode.
// Can halt here or fall back to the local filesystem.
if err == stuffbin.ErrNoID {
// First argument is to the root to mount the files in the FileSystem
// and the rest of the arguments are paths to embed.
fs, err = stuffbin.NewLocalFS("/",
"./", "bar.txt:/virtual/path/bar.txt")
if err != nil {
log.Fatalf("error falling back to local filesystem: %v", err)
}
} else {
log.Fatalf("error reading stuffed binary: %v", err)
}
}
fmt.Println("loaded files", fs.List())
// Read the file 'foo'.
f, err := fs.Get("foo.txt")
if err != nil {
log.Fatalf("error reading foo.txt: %v", err)
}
log.Println("foo.txt =", string(f.ReadBytes()))
// Read the file 'bar'.
f, err = fs.Get("/virtual/path/bar.txt")
if err != nil {
log.Fatalf("error reading /virtual/path/bar.txt: %v", err)
}
log.Println("/virtual/path/bar.txt =", string(f.ReadBytes()))
fmt.Println("stuffed files:")
for _, f := range fs.List() {
fmt.Println("\t", f)
}
// Compile templates with the helpers:
// err, tpl := stuffbin.ParseTemplatesGlob(nil, fs, "/templates/*.html")
//
// Template func map.
// mp := map[string]interface{}{
// "Foo": func() string {
// return "func"
// },
// }
// err, tpl := stuffbin.ParseTemplates(mp, fs, "/templates/index.html", "/templates/hello.html")
// Expose an HTTP file server.
// Try http://localhost:8000/static/foo.txt
// Try http://localhost:8000/static/virtual/path/bar.txt
// Try http://localhost:8000/static/subdir/baz.txt
http.Handle("/static/", http.StripPrefix("/static/", fs.FileServer()))
log.Println("listening on :8000")
http.ListenAndServe(":8000", nil)
}
Licensed under the MIT License.
Overall
Maintained
Code review
Go module
Latest release
Releases this year
Compress and embed static files and assets into Go binaries and access them with a virtual file system in production
Go
205
25 commits
updated Jun 28, 2026
stuffbin is a utility + package to compress and embed static files and assets into Go binaries for distribution. It supports falling back to the local file system when no embedded assets are available, for instance, in development mode. stuffbin is inspired by zgok but is simpler and has better abstractions.
Update (June 2026): stuffbin seems to be feature complete with no changes required in ~3 years. It is actively used in critical production systems.
Go 1.16 introduced the //go:embed directive that allows embedding of files into Go binaries without any external utilities. stuffbin offers a few key advantages over native embedding in its current form.
/home/local/path/file.txt as /app/file.txt).//go:embed directives.../../path).
Go embed does not permit embedding of files outside of a .go file's directory. This makes it difficult for programs structured in a cmd directory to embed files outside it.
stuffbin compresses and embeds arbitrary files to the end of Go binaries. This does not affect the normal execution of the binary as the compressed data that is appended beyond the binary's original size is simply ignored by the operating system. When a stuffed application is executed, stuffbin reads the compressed bytes from self (the executable), uncompresses the files on the fly into an in-memory filesystem, and provides a FileSystem interface to access them. This enables Go applications that have external file dependencies to be shipped a single fat binary, commonly, web applications that have static file and template dependencies.
go install github.com/knadh/stuffbin/...@latest
For macOS/Linux users, you can install via brew
$ brew install stuffbin
# -a, -in, and -out params followed by the paths of files to embed.
# To normalize paths, aliases can be suffixed with a colon.
stuffbin -a stuff -in /path/to/exe -out /path/to/new.exe \
static/file1.css static/file2.pdf /somewhere/else/file3.txt:/static/file3.txt
stuffbin -a id -in /path/to/new/exe
stuffbin -a unstuff -in /path/to/new/exe -out assets.zip
To test this, cd into ./mock and run go run mock.go
package main
import (
"fmt"
"log"
"net/http"
"os"
"github.com/knadh/stuffbin"
)
func main() {
// Get self executable path.
path, err := os.Executable()
if err != nil {
log.Fatalf("error getting executable path: %v", err)
}
// Read stuffed data from self.
fs, err := stuffbin.UnStuff(path)
if err != nil {
// Binary is unstuffed or is running in dev mode.
// Can halt here or fall back to the local filesystem.
if err == stuffbin.ErrNoID {
// First argument is to the root to mount the files in the FileSystem
// and the rest of the arguments are paths to embed.
fs, err = stuffbin.NewLocalFS("/",
"./", "bar.txt:/virtual/path/bar.txt")
if err != nil {
log.Fatalf("error falling back to local filesystem: %v", err)
}
} else {
log.Fatalf("error reading stuffed binary: %v", err)
}
}
fmt.Println("loaded files", fs.List())
// Read the file 'foo'.
f, err := fs.Get("foo.txt")
if err != nil {
log.Fatalf("error reading foo.txt: %v", err)
}
log.Println("foo.txt =", string(f.ReadBytes()))
// Read the file 'bar'.
f, err = fs.Get("/virtual/path/bar.txt")
if err != nil {
log.Fatalf("error reading /virtual/path/bar.txt: %v", err)
}
log.Println("/virtual/path/bar.txt =", string(f.ReadBytes()))
fmt.Println("stuffed files:")
for _, f := range fs.List() {
fmt.Println("\t", f)
}
// Compile templates with the helpers:
// err, tpl := stuffbin.ParseTemplatesGlob(nil, fs, "/templates/*.html")
//
// Template func map.
// mp := map[string]interface{}{
// "Foo": func() string {
// return "func"
// },
// }
// err, tpl := stuffbin.ParseTemplates(mp, fs, "/templates/index.html", "/templates/hello.html")
// Expose an HTTP file server.
// Try http://localhost:8000/static/foo.txt
// Try http://localhost:8000/static/virtual/path/bar.txt
// Try http://localhost:8000/static/subdir/baz.txt
http.Handle("/static/", http.StripPrefix("/static/", fs.FileServer()))
log.Println("listening on :8000")
http.ListenAndServe(":8000", nil)
}
Licensed under the MIT License.
Overall
Maintained
Code review
Go module
Latest release
Releases this year