9.6 Embedding Files (Go 1.16+)

The embed package, introduced in Go 1.16, allows you to embed files and directories directly into your Go binary at compile time. This eliminates the need for external files and simplifies deployment.

Embed Directive

What is file embedding?

File embedding includes files and directories in the compiled binary. The embedded content becomes part of the executable, accessible at runtime without external files.

Embedding exists to create single binaries (no need for separate data files), simplify deployment (one file to distribute), eliminate file system dependencies (works even if filesystem is read-only), and enable version control (data files are versioned with code).

The //go:embed directive tells the compiler to include files in the binary:

package main

import (
    _ "embed"
    "fmt"
)

//go:embed data.txt
var data string

func main() {
    fmt.Println(data)
}

The blank import _ "embed" is required to enable the embed feature. The blank identifier means we're importing for side effects only.

Basic syntax

//go:embed path/to/file
var variableName type

This syntax embeds files at compile time, uses type safety (variable type determines how data is embedded), and provides minimal syntax for a powerful feature.

Embedding Files

Embedding as string

package main

import (
    _ "embed"
    "fmt"
)

//go:embed config.txt
var config string

func main() {
    fmt.Println("Config:", config)
}

String embedding is useful for text files (perfect for configuration, templates, etc.), provides simple access (direct string access), and is immutable (string is read-only, safe).

Embedding as []byte

package main

import (
    _ "embed"
    "fmt"
)

//go:embed image.png
var imageData []byte

func main() {
    fmt.Printf("Image size: %d bytes\n", len(imageData))
}

Byte slice embedding is useful for binary files (images, executables, etc.), provides flexibility (can be used with any binary format), and stores data as a byte slice in memory.

Embedding as embed.FS

package main

import (
    "embed"
    "fmt"
    "io/fs"
)

//go:embed templates/*
var templateFS embed.FS

func main() {
    files, _ := fs.ReadDir(templateFS, "templates")
    for _, file := range files {
        fmt.Println(file.Name())
    }
}

embed.FS is useful because it embeds entire directory trees, implements the fs.FS interface, and allows access to multiple embedded files.

Multiple files

package main

import (
    _ "embed"
    "fmt"
)

//go:embed file1.txt file2.txt
var files embed.FS

func main() {
    data1, _ := files.ReadFile("file1.txt")
    data2, _ := files.ReadFile("file2.txt")
    fmt.Println(string(data1))
    fmt.Println(string(data2))
}

Multiple file embedding allows you to group related files together, keep related data organized, and use a single embed directive for multiple files.

Embedding Directories

Basic directory embedding

package main

import (
    "embed"
    "fmt"
    "io/fs"
)

//go:embed static/*
var staticFiles embed.FS

func main() {
    fs.WalkDir(staticFiles, ".", func(path string, d fs.DirEntry, err error) error {
        if !d.IsDir() {
            fmt.Println(path)
        }
        return nil
    })
}

Directory embedding is useful for web assets (HTML, CSS, JavaScript files), templates (multiple template files), and resources (images, fonts, etc.).

Directory structure

project/
├── main.go
└── static/
    ├── index.html
    ├── style.css
    └── script.js
//go:embed static/*
var staticFiles embed.FS

The * wildcard includes all files in the directory recursively. Without it, only the directory itself is embedded (empty).

Excluding files

//go:embed static/*
//go:embed !static/*.tmp
var staticFiles embed.FS

Exclusions are useful to exclude build artifacts (generated files), development files (dev-only files), and sensitive data (files with secrets).

Nested directories

//go:embed templates/html/* templates/css/*
var templates embed.FS

func main() {
    html, _ := templates.ReadFile("templates/html/index.html")
    css, _ := templates.ReadFile("templates/css/style.css")
}

Nested embedding maintains directory structure, preserves original paths, and provides flexibility to embed specific subdirectories.

Use Cases

1. Web server with embedded assets

package main

import (
    "embed"
    "net/http"
)

//go:embed static/*
var staticFiles embed.FS

func main() {
    http.Handle("/static/", http.FileServer(http.FS(staticFiles)))
    http.ListenAndServe(":8080", nil)
}

Embedded web assets create a single binary (no need to deploy separate files), improve portability (works anywhere the binary runs), and enable versioning (assets versioned with code).

2. Configuration files

package main

import (
    _ "embed"
    "encoding/json"
    "fmt"
)

//go:embed config.json
var configJSON string

type Config struct {
    Host string `json:"host"`
    Port int    `json:"port"`
}

func loadConfig() (*Config, error) {
    var config Config
    return &config, json.Unmarshal([]byte(configJSON), &config)
}

Embedded config provides default values (built-in default configuration), eliminates external files (works without file system), and enables version control (config changes tracked in git).

3. Templates

package main

import (
    "embed"
    "html/template"
    "net/http"
)

//go:embed templates/*
var templates embed.FS

func handler(w http.ResponseWriter, r *http.Request) {
    tmpl, _ := template.ParseFS(templates, "templates/*.html")
    tmpl.ExecuteTemplate(w, "index.html", data)
}

Embedded templates create a single binary (templates included in executable), eliminate file I/O (faster template loading), and simplify deployment.

4. SQL migrations

package main

import (
    "embed"
    "database/sql"
)

//go:embed migrations/*.sql
var migrations embed.FS

func runMigrations(db *sql.DB) error {
    files, _ := migrations.ReadDir("migrations")
    for _, file := range files {
        sql, _ := migrations.ReadFile("migrations/" + file.Name())
        db.Exec(string(sql))
    }
    return nil
}

Embedded migrations enable version control (migrations tracked with code), atomic deployment (migrations included in binary), and work in containerized environments (no external files).

5. Default data

package main

import (
    _ "embed"
    "encoding/csv"
    "strings"
)

//go:embed data/countries.csv
var countriesCSV string

func loadCountries() ([][]string, error) {
    return csv.NewReader(strings.NewReader(countriesCSV)).ReadAll()
}

Embedded data is useful for reference data (include lookup tables, etc.), enables offline operation (works without network), and ensures consistency (same data in all deployments).

6. CLI help text

package main

import (
    _ "embed"
    "fmt"
)

//go:embed help.txt
var helpText string

func showHelp() {
    fmt.Println(helpText)
}

Best practices

1. Use appropriate types

// ✅ GOOD: String for text
//go:embed config.txt
var config string

// ✅ GOOD: []byte for binary
//go:embed image.png
var image []byte

// ✅ GOOD: embed.FS for directories
//go:embed static/*
var static embed.FS

2. Organize embedded files

project/
├── main.go
├── embed/
│   ├── config/
│   │   └── app.json
│   ├── templates/
│   │   └── index.html
│   └── static/
│       └── style.css

3. Document embedded files

//go:embed config/app.json
// Embedded application configuration.
// This file is included in the binary at compile time.
var config string

4. Consider file size

5. Use for deployment, not development

//go:build !dev
//go:embed static/*
var static embed.FS

// In dev, read from filesystem
// In production, use embedded files

Limitations

1. Compile-time only

Files must exist at compile time. You can't embed files created at runtime.

2. No modification

Embedded files are read-only. You can't modify them at runtime.

3. Binary size

Large embedded files increase binary size. Consider the trade-off.

4. Path restrictions

Paths are relative to the package directory. Can't embed files outside the module.

5. Build tags

Embed directives respect build tags, but all referenced files must exist for all build configurations.

The embed package is a powerful feature that simplifies deployment by including files directly in binaries. Use it for assets, configuration, templates, and other files that should be part of your application distribution.