go / embed
I use //go:embed to bundle files into a Go binary at compile time.
Single file
Embed a single file as a byte slice:
import _ "embed"
//go:embed schema.sql
var schema []byte
func initDB(db *sql.DB) error {
_, err := db.Exec(string(schema))
return err
}
Or as a string:
//go:embed version.txt
var version string
Multiple files
Embed multiple files into an embed.FS:
import "embed"
//go:embed templates/*.html
var templatesFS embed.FS
func loadTemplate(name string) ([]byte, error) {
return templatesFS.ReadFile("templates/" + name)
}
The path in ReadFile must match the embedded path exactly,
including the directory prefix.
Patterns
Patterns work like filepath.Glob:
//go:embed static/*
var staticFS embed.FS
//go:embed *.sql
var migrations embed.FS
//go:embed templates/*.html templates/*.css
var assetsFS embed.FS
Multiple directives can target the same variable:
//go:embed index.json
//go:embed *.gotmpl
//go:embed *.json
var templatesFS embed.FS
HTTP file server
Serve embedded files over HTTP:
import (
"embed"
"io/fs"
"net/http"
)
//go:embed static/*
var staticFS embed.FS
func main() {
// Strip "static/" prefix so /app.css serves static/app.css
stripped, _ := fs.Sub(staticFS, "static")
http.Handle("/", http.FileServer(http.FS(stripped)))
http.ListenAndServe(":8080", nil)
}
Templates
Parse a layout shell and one page template into a single template
set. The page defines {{define "content"}}...{{end}}; the layout
renders it via {{template "content" .}}:
//go:embed ui/*.html
var uiFS embed.FS
func parseTemplates(page string) (*template.Template, error) {
return template.New("").ParseFS(uiFS, "ui/index.html", "ui/"+page)
}
One shared chrome (header, footer, scripts), many pages, no duplication.
Migrations
Bundle SQL migrations with the binary so production deploys carry their own DDL:
const MigrateDir = "db/migrate"
//go:embed db/migrate/*.sql
var MigrateFS embed.FS
Load and run them via io/fs:
entries, _ := fs.ReadDir(MigrateFS, MigrateDir)
for _, e := range entries {
sql, _ := fs.ReadFile(MigrateFS, MigrateDir+"/"+e.Name())
db.Exec(string(sql))
}
No separate migration directory to ship, no path-finding code.
When to use
- CLI tools that need bundled templates, configs, or assets
- Web servers with static files (HTML, CSS, JS, images)
- Database migrations shipped with the binary
- Any deployment where fewer moving parts is valuable
When not to use
- Files that change frequently without code changes
- Very large assets (increases binary size and memory usage)
- Content that users should be able to modify at runtime
See fingerprint for cache-busting embedded assets in production web apps.