Hi there! I'm Shrijith Venkatrama, founder of Hexmos. Right now, I’m building LiveAPI, a first of its kind tool for helping you automatically index API endpoints across all your repositories. LiveAPI helps you discover, understand and use APIs in large tech infrastructures with ease.
Embedding files in a Go binary lets you bundle assets like templates, images, or config files directly into your executable. This means no external dependencies at runtime, simpler deployments, and a single file to ship. Go’s embed
package, introduced in Go 1.16, makes this straightforward. This post dives into how to use it, with practical examples, trade-offs, and tips to keep your code clean and efficient.
We’ll cover why embedding matters, how to use the embed
package, handling different file types, real-world use cases, and best practices. Each section includes complete, runnable code examples with outputs.
Why Embed Files in Your Go Binary?
Embedding files solves real problems for developers. Instead of juggling external files during deployment, you can pack everything into one binary. This is great for CLI tools, web servers, or microservices where you want a single artifact. It reduces complexity, avoids “file not found” errors, and makes your app portable.
Key benefits:
- Single binary: One file to deploy, no extra assets.
- No runtime file I/O: Files are in memory, reducing disk access.
- Cross-platform: Works everywhere Go runs, no path issues.
Downsides:
- Larger binary size (more on this later).
- Files are read-only (you can’t modify them at runtime).
- Rebuilding required to update embedded files.
For more on why single binaries are awesome, check this Go blog post.
Getting Started with the embed
Package
The embed
package is built into Go since version 1.16. It’s simple: you use the //go:embed
directive to include files or directories, and access them via variables. No external tools or libraries needed.
Let’s start with a basic example: embedding a single text file.
// main.go
package main
import (
"embed"
"fmt"
)
//go:embed hello.txt
var message string
func main() {
fmt.Println(message)
}
// Output:
// Contents of hello.txt
Steps:
- Create a file
hello.txt
with some text (e.g., “Hello, embedded world!”). - Use
//go:embed hello.txt
above a variable (here,message
). - The variable
message
holds the file’s contents as a string. - Run
go run main.go
to see the output.
Notes:
- The variable must be of type
string
,[]byte
, orembed.FS
. - The
//go:embed
directive must be right above the variable declaration, with no blank lines. - Paths are relative to the Go file.
This example is simple but shows the core idea. Let’s scale it up.
Embedding Multiple Files and Directories
For real projects, you’ll likely need to embed multiple files or entire directories. The embed.FS
type lets you treat embedded files like a virtual filesystem. This is perfect for web assets, templates, or configuration files.
Here’s an example embedding a directory of text files.
// main.go
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed files/*
var content embed.FS
func main() {
// List all files in the embedded directory
entries, err := fs.ReadDir(content, "files")
if err != nil {
fmt.Println("Error:", err)
return
}
for _, entry := range entries {
// Read each file's content
data, err := content.ReadFile("files/" + entry.Name())
if err != nil {
fmt.Println("Error reading", entry.Name(), ":", err)
continue
}
fmt.Printf("File: %s\nContent: %s\n\n", entry.Name(), string(data))
}
}
// Directory structure:
// files/
// ├── note1.txt (contains "First note")
// ├── note2.txt (contains "Second note")
// Output:
// File: note1.txt
// Content: First note
//
// File: note2.txt
// Content: Second note
Steps:
- Create a
files
directory withnote1.txt
andnote2.txt
. - Use
//go:embed files/*
to embed all files in thefiles
directory. - Use
embed.FS
to access the files like a filesystem. - Use
fs.ReadDir
andcontent.ReadFile
to read file contents. - Run
go run main.go
.
Tips:
- Use
files/*
to embed all files in a directory (wildcards are supported). -
embed.FS
implements theio/fs.FS
interface, so you can use standard library functions likefs.ReadDir
. - Paths in
embed.FS
are relative to the embedded root.
For more on embed.FS
, see the Go documentation.
Handling Different File Types
You can embed any file type—text, images, JSON, HTML, etc. The trick is choosing the right variable type: string
for single text files, []byte
for binary files, or embed.FS
for multiple files or complex structures.
Let’s embed an image and a JSON config file, then use them in a program.
// main.go
package main
import (
"embed"
"encoding/json"
"fmt"
)
//go:embed assets/config.json
//go:embed assets/logo.png
var assets embed.FS
type Config struct {
AppName string `json:"appName"`
Version string `json:"version"`
}
func main() {
// Read JSON config
configData, err := assets.ReadFile("assets/config.json")
if err != nil {
fmt.Println("Error reading config:", err)
return
}
var cfg Config
if err := json.Unmarshal(configData, &cfg); err != nil {
fmt.Println("Error parsing JSON:", err)
return
}
fmt.Printf("Config: %+v\n", cfg)
// Read image file (just check size for demo)
imgData, err := assets.ReadFile("assets/logo.png")
if err != nil {
fmt.Println("Error reading image:", err)
return
}
fmt.Printf("Image size: %d bytes\n", len(imgData))
}
// Directory structure:
// assets/
// ├── config.json (contains {"appName":"MyApp","version":"1.0.0"})
// ├── logo.png (any small PNG image)
// Output:
// Config: {AppName:MyApp Version:1.0.0}
// Image size: 12345 bytes (actual size depends on your logo.png)
Steps:
- Create an
assets
directory withconfig.json
andlogo.png
. - Embed both files using
//go:embed
withembed.FS
. - Read and parse the JSON file using
json.Unmarshal
. - Read the image file as bytes (useful for serving in a web app).
- Run
go run main.go
.
Notes:
- For binary files like images, use
[]byte
orembed.FS
to avoid encoding issues. - JSON files are great for configs; parse them into structs for type safety.
- Images can be any format (PNG, JPEG, etc.), but you’ll need appropriate libraries to process them (e.g.,
image/png
).
Real-World Use Case: Serving Embedded Web Assets
A common use case is embedding web assets for a simple HTTP server. Let’s build a small web server that serves an embedded HTML file and a CSS file.
// main.go
package main
import (
"embed"
"fmt"
"net/http"
)
//go:embed static/*
var static embed.FS
func main() {
// Serve static files
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
path := "static" + r.URL.Path
if path == "static/" {
path = "static/index.html"
}
data, err := static.ReadFile(path)
if err != nil {
http.Error(w, "File not found", http.StatusNotFound)
return
}
if r.URL.Path[len(r.URL.Path)-4:] == ".css" {
w.Header().Set("Content-Type", "text/css")
}
w.Write(data)
})
fmt.Println("Server running on :8080")
http.ListenAndServe(":8080", nil)
}
// Directory structure:
// static/
// ├── index.html (contains "<!DOCTYPE html><html><head><link rel='stylesheet' href='style.css'></head><body><h1>Hello, Go!</h1></body></html>")
// ├── style.css (contains "h1 { color: blue; }")
// Output:
// Server running on :8080
// (Visit http://localhost:8080 in a browser to see the styled "Hello, Go!" page)
Steps:
- Create a
static
directory withindex.html
andstyle.css
. - Embed the directory using
//go:embed static/*
. - Set up an HTTP server to serve files from
embed.FS
. - Run
go run main.go
and visithttp://localhost:8080
.
Notes:
- Use
http.ServeContent
for production to handle caching and ranges. - Set proper
Content-Type
headers for CSS, JS, or images. - This approach is ideal for small, self-contained web apps.
For advanced HTTP serving, check net/http documentation.
Best Practices and Trade-Offs
Embedding files is powerful, but it comes with trade-offs. Here’s a quick guide to use it effectively.
Aspect | Pros | Cons |
---|---|---|
Binary Size | Single file, easy to distribute | Can grow large with many/big files |
Performance | Fast access (in-memory) | Increases memory usage |
Updates | No external file management | Requires rebuild to update files |
Use Cases | CLI tools, small web servers | Not ideal for huge assets (e.g., videos) |
Best practices:
- Use sparingly: Embed only what’s necessary to keep binary size manageable.
-
Organize files: Use clear directory structures (e.g.,
assets/
,static/
) for clarity. - Compress assets: Minify CSS/JS or compress images before embedding.
- Version control: Track embedded files in git to avoid surprises.
- Test thoroughly: Verify embedded files work as expected in production.
When not to embed:
- If files change frequently (use a config server or database).
- For very large files (e.g., videos), consider external storage.
Next Steps for Embedding Files
Embedding files in Go binaries with the embed
package is a game-changer for building portable, self-contained applications. You’ve seen how to embed single files, directories, and different file types, plus a real-world web server example. The key is to balance binary size with convenience and use embed.FS
for flexibility.
Try embedding assets in your next CLI tool or web server. Experiment with different file types and check the binary size with ls -lh
after building. If you’re curious about advanced use cases, explore combining embed
with libraries like html/template
for dynamic pages or image
for processing embedded images.
Top comments (0)