DEV Community

Shrijith Venkatramana
Shrijith Venkatramana

Posted on

Pack Your Go Binary: Embedding Files Made Simple

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
Enter fullscreen mode Exit fullscreen mode

Steps:

  1. Create a file hello.txt with some text (e.g., “Hello, embedded world!”).
  2. Use //go:embed hello.txt above a variable (here, message).
  3. The variable message holds the file’s contents as a string.
  4. Run go run main.go to see the output.

Notes:

  • The variable must be of type string, []byte, or embed.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
Enter fullscreen mode Exit fullscreen mode

Steps:

  1. Create a files directory with note1.txt and note2.txt.
  2. Use //go:embed files/* to embed all files in the files directory.
  3. Use embed.FS to access the files like a filesystem.
  4. Use fs.ReadDir and content.ReadFile to read file contents.
  5. Run go run main.go.

Tips:

  • Use files/* to embed all files in a directory (wildcards are supported).
  • embed.FS implements the io/fs.FS interface, so you can use standard library functions like fs.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)
Enter fullscreen mode Exit fullscreen mode

Steps:

  1. Create an assets directory with config.json and logo.png.
  2. Embed both files using //go:embed with embed.FS.
  3. Read and parse the JSON file using json.Unmarshal.
  4. Read the image file as bytes (useful for serving in a web app).
  5. Run go run main.go.

Notes:

  • For binary files like images, use []byte or embed.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)
Enter fullscreen mode Exit fullscreen mode

Steps:

  1. Create a static directory with index.html and style.css.
  2. Embed the directory using //go:embed static/*.
  3. Set up an HTTP server to serve files from embed.FS.
  4. Run go run main.go and visit http://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)