Bundle Static Web Assets into a Go Binary with embed
Use Go's embed package to compile static files into a single binary and serve them without runtime file-system dependencies.
01 Oct 2026, 15:34 UTC

The problem: missing static files in production
You deploy a Go web service in a container. The container starts, but browsers receive 404s for CSS, JavaScript, or HTML templates. The files exist on your laptop, yet they are absent from the image or hidden behind a broken volume mount. Debugging runtime file-system paths across CI, staging, and production is fragile.
Go's standard library embed package, stable since Go 1.16, compiles static files directly into the executable. The result is a single binary that carries its own assets and does not depend on external directories at runtime.
How //go:embed works
The //go:embed directive tells the compiler to read files at build time. A directory becomes an embed.FS, a read-only file system type, while a single file can be captured as a string or []byte. You must import the embed package in the same file; otherwise the compiler silently ignores the directive.
Patterns must be string literals known at compile time, and the target files must live inside the current package directory. You cannot reference a parent directory or construct the path from a variable.
Worked example: serve an embedded directory
Create a module and a static directory:
# Run in your project directory
mkdir -p embeddemo/static
cd embeddemo
go mod init example.com/embeddemo
Add static/index.html:
<!doctype html>
<html>
<body><h1>Embedded asset</h1></body>
</html>
Create main.go in the module root:
package main
import (
"embed"
"log"
"net/http"
)
//go:embed static
var assets embed.FS
func main() {
mux := http.NewServeMux()
mux.Handle("/", http.FileServer(http.FS(assets)))
log.Println("listening on :8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatal(err)
}
}
The directive //go:embed static bundles everything under static/ into assets. Because assets is an embed.FS, it implements http.FileSystem, so http.FS(assets) adapts it for http.FileServer.
Build and run from a different directory to confirm the binary no longer needs the source folder:
# Build from the module root
go build -o embeddemo .
# Run from /tmp so the working directory contains no static files
cd /tmp
/path/to/embeddemo/embeddemo
Then, in another terminal:
curl -i http://localhost:8080/
Expected check: the response should return the HTML from static/index.html with a 200 status. Risk: because assets are fixed at build time, changing static/index.html requires a rebuild and redeploy before visitors see the update.
Trade-offs and limitations
- Binary size. Embedded assets increase the executable size. Large images or videos can also slow builds, so measure before adopting this for media-heavy sites.
- No runtime updates. To change an asset, you must rebuild and redeploy the binary. This fits immutable, versioned assets but not user-generated content or frequently edited stylesheets.
- Directory restrictions. You cannot embed files outside the package directory, and embed patterns cannot be variables.
Verify the impact
Compare binary size before and after adding assets:
# From the module root
ls -lh embeddemo
# Rebuild after adding assets, then compare
go build -o embeddemo .
ls -lh embeddemo
You can inspect symbols with go tool nm embeddemo to confirm the embedded file system is present in the binary.
When to use it
Start with small, stable assets such as HTML templates, CSS, and JavaScript. Keep large or frequently changing media on a CDN or object store and fetch it at runtime. If your deployment pipeline already handles static files reliably, embed may add complexity without benefit. For containerized Go services that break when a volume mount is missing, a single self-contained binary is often the simpler choice.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.