Use the embed package (available in Go 1.16+) to compile configuration files directly into your binary, eliminating the need for external file dependencies at runtime. This approach ensures your application is self-contained and prevents missing file errors in production environments.
For simple string-based configs like JSON or YAML, embed the file as a string and unmarshal it directly into your struct. This is the most common pattern for static configurations.
package main
import (
"encoding/json"
"embed"
"fmt"
"log"
)
//go:embed config.json
var configData []byte
type Config struct {
ServerPort int `json:"server_port"`
Database string `json:"database"`
}
func main() {
var cfg Config
if err := json.Unmarshal(configData, &cfg); err != nil {
log.Fatal(err)
}
fmt.Printf("Config loaded: Port %d, DB %s\n", cfg.ServerPort, cfg.Database)
}
If you need to manage multiple files, directories, or require file-like operations (such as reading specific paths or listing contents), embed a FS (File System) instead. This gives you a virtual file system accessible via os-like functions.
package main
import (
"embed"
"fmt"
"io/fs"
"log"
)
//go:embed configs/*.yaml
var ConfigFS embed.FS
func main() {
// Read a specific file from the embedded FS
data, err := ConfigFS.ReadFile("configs/production.yaml")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Loaded %d bytes from production.yaml\n", len(data))
// Walk through all embedded files
err = fs.WalkDir(ConfigFS, "configs", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
fmt.Printf("Found embedded file: %s\n", path)
return nil
})
if err != nil {
log.Fatal(err)
}
}
Remember that the //go:embed directive must be placed immediately before the variable declaration in the same file. The path is relative to the directory containing the Go source file. While this makes distribution easier, it prevents runtime hot-swapping of configuration without rebuilding the binary, so use this primarily for default configurations or immutable settings.