Desde o Go 1.16, a diretiva //go:embed permite embutir arquivos e diretórios inteiros dentro do binário compilado. Isso significa que seu deploy se resume a um único arquivo executável — sem se preocupar com caminhos de templates, arquivos de configuração ou assets estáticos que podem estar faltando em produção.
Neste guia, você vai aprender a usar embed.FS, embutir templates HTML, servir assets estáticos via HTTP e combinar tudo em aplicações reais.
Por que Embutir Arquivos no Binário
Em Go, o binário compilado já é autocontido — sem runtime, sem dependências. Mas quando sua aplicação precisa de templates HTML, arquivos SQL de migração, configurações padrão ou assets de frontend, você volta a depender de arquivos externos no filesystem. A diretiva //go:embed resolve isso:
- Deploy simplificado: um binário, zero arquivos avulsos
- Reprodutibilidade: o binário sempre inclui a versão correta dos arquivos
- Imagens Docker menores: sem copiar diretórios extras para a imagem final
- Segurança: arquivos embutidos não podem ser modificados em produção
Embutindo um Arquivo Simples
O caso mais básico — embutir um único arquivo como string ou []byte:
package main
import (
_ "embed"
"fmt"
)
//go:embed version.txt
var version string
//go:embed config-default.json
var defaultConfig []byte
func main() {
fmt.Println("Versão:", version)
fmt.Printf("Config padrão: %s\n", defaultConfig)
}
Crie o arquivo version.txt com o conteúdo 1.0.0 e config-default.json com um JSON qualquer. Ao compilar, os arquivos são incorporados ao binário:
go build -o myapp .
rm version.txt config-default.json # binário já contém os dados
./myapp # funciona normalmente
Note o import _ "embed" — ele é obrigatório quando você usa //go:embed com tipos primitivos (string ou []byte). Sem ele, o compilador retorna erro.
Embutindo Diretórios com embed.FS
Para múltiplos arquivos ou diretórios inteiros, use o tipo embed.FS:
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed templates/*
var templateFiles embed.FS
//go:embed static/*
var staticFiles embed.FS
func main() {
// Listar arquivos embutidos
fs.WalkDir(templateFiles, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
fmt.Println("Embutido:", path)
}
return nil
})
// Ler um arquivo específico
data, err := templateFiles.ReadFile("templates/index.html")
if err != nil {
panic(err)
}
fmt.Printf("Template: %s\n", data)
}
O padrão templates/* embute todos os arquivos no diretório templates/. Você pode usar múltiplos padrões:
//go:embed static/css/* static/js/* static/images/*
var assets embed.FS
Servindo Assets Estáticos via HTTP
Combinar embed.FS com o net/http da standard library é uma das aplicações mais práticas. Perfeito para aplicações HTMX ou SPAs com frontend embutido:
package main
import (
"embed"
"io/fs"
"log"
"net/http"
)
//go:embed static/*
var staticFiles embed.FS
func main() {
// Remover o prefixo "static/" para servir na raiz
staticFS, err := fs.Sub(staticFiles, "static")
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("API Go com assets embutidos!"))
})
log.Println("Servidor rodando em :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
A função fs.Sub() cria um sub-filesystem que remove o prefixo do diretório. Sem ela, os caminhos dos arquivos incluiriam static/ — e as URLs ficariam como /static/static/style.css.
Templates HTML com embed.FS
Para aplicações web que usam templates server-side, combine embed.FS com html/template. Se você trabalha com templates type-safe, veja também o templ:
package main
import (
"embed"
"html/template"
"log"
"net/http"
)
//go:embed templates/*.html
var templateFiles embed.FS
var tmpl *template.Template
func init() {
tmpl = template.Must(template.ParseFS(templateFiles, "templates/*.html"))
}
type PageData struct {
Title string
Items []string
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
data := PageData{
Title: "Minha App Go",
Items: []string{"Item 1", "Item 2", "Item 3"},
}
if err := tmpl.ExecuteTemplate(w, "index.html", data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /", homeHandler)
log.Fatal(http.ListenAndServe(":8080", mux))
}
A função template.ParseFS() aceita embed.FS diretamente — sem precisar ler arquivos do disco. Os templates são parseados uma vez no init e reutilizados em cada requisição.
Migrações SQL Embutidas
Outro caso de uso popular é embutir arquivos SQL para migrações de banco de dados. Perfeito para projetos que usam PostgreSQL ou outros bancos relacionais com sqlc:
package migrations
import (
"embed"
"io/fs"
"sort"
"strings"
)
//go:embed sql/*.sql
var migrationFiles embed.FS
func GetMigrations() ([]string, error) {
entries, err := fs.ReadDir(migrationFiles, "sql")
if err != nil {
return nil, err
}
var migrations []string
for _, e := range entries {
if !e.IsDir() && strings.HasSuffix(e.Name(), ".sql") {
migrations = append(migrations, e.Name())
}
}
sort.Strings(migrations)
return migrations, nil
}
func ReadMigration(name string) (string, error) {
data, err := migrationFiles.ReadFile("sql/" + name)
if err != nil {
return "", err
}
return string(data), nil
}
Organize os arquivos com prefixo numérico (001_create_users.sql, 002_add_index.sql) para garantir a ordem correta.
CLI com Help Embutido
Para ferramentas de linha de comando, embutir textos de ajuda e exemplos mantém o binário autocontido:
package main
import (
_ "embed"
"fmt"
"os"
)
//go:embed help.txt
var helpText string
//go:embed examples/basic.yaml
var basicExample string
func main() {
if len(os.Args) > 1 && os.Args[1] == "--help" {
fmt.Print(helpText)
return
}
if len(os.Args) > 1 && os.Args[1] == "--example" {
fmt.Print(basicExample)
return
}
fmt.Println("Executando aplicação...")
}
Regras e Limitações do go:embed
Antes de usar //go:embed, conheça as regras:
- A diretiva deve estar imediatamente acima da declaração da variável — sem linhas em branco entre elas
- Variáveis devem ser do tipo
string,[]byteouembed.FS— outros tipos não são aceitos - Apenas arquivos no módulo atual — não é possível embutir arquivos de fora do módulo
- Arquivos ocultos (começando com
.ou_) são ignorados por padrão — useall:para incluí-los://go:embed all:templates - O padrão
*não inclui subdiretórios — use**ou liste explicitamente://go:embed templates/** - Caminhos são relativos ao package, não ao módulo raiz
// CORRETO: diretiva seguida da variável
//go:embed data.json
var data []byte
// ERRADO: linha em branco entre diretiva e variável
//go:embed data.json
var data []byte // erro de compilação
Performance: Embed vs Leitura em Disco
Arquivos embutidos são mapeados na memória do binário — a leitura é instantânea, sem syscalls de I/O. Mas considere os trade-offs:
| Aspecto | embed.FS | Leitura em disco |
|---|---|---|
| Velocidade de leitura | Instantânea (memória) | Depende do disco |
| Tamanho do binário | Aumenta | Não afeta |
| Atualização | Requer recompilação | Editar arquivo basta |
| Deploy | Um arquivo | Binário + diretórios |
Quando usar embed: templates, migrações SQL, assets de frontend, arquivos de configuração padrão, textos de ajuda de CLI.
Quando usar disco: arquivos grandes (vídeos, datasets), conteúdo que muda sem redeploy, uploads de usuários.
Para aplicações web com WebAssembly, embutir o .wasm compilado no servidor Go é uma combinação poderosa — o servidor serve tanto a API quanto o binário WASM.
Combinando com Table-Driven Tests
Embutir fixtures de teste é outro uso prático. Combine com table-driven tests para iterar sobre múltiplos arquivos de entrada:
package parser
import (
"embed"
"io/fs"
"strings"
"testing"
)
//go:embed testdata/valid/*.json
var validFixtures embed.FS
//go:embed testdata/invalid/*.json
var invalidFixtures embed.FS
func TestParseValidFiles(t *testing.T) {
entries, _ := fs.ReadDir(validFixtures, "testdata/valid")
for _, e := range entries {
t.Run(e.Name(), func(t *testing.T) {
data, _ := validFixtures.ReadFile("testdata/valid/" + e.Name())
_, err := Parse(data)
if err != nil {
t.Errorf("arquivo válido %s retornou erro: %v", e.Name(), err)
}
})
}
}
Esse padrão é extensível — adicionar um novo caso de teste é simplesmente adicionar um arquivo JSON na pasta testdata/.
Exemplo Completo: Web Server com Frontend Embutido
Um servidor Go completo que serve uma SPA com assets embutidos e uma API REST:
package main
import (
"embed"
"encoding/json"
"io/fs"
"log"
"net/http"
)
//go:embed frontend/dist/*
var frontendFiles embed.FS
func main() {
frontendFS, err := fs.Sub(frontendFiles, "frontend/dist")
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
// API
mux.HandleFunc("GET /api/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})
// Frontend (SPA)
mux.Handle("GET /", http.FileServer(http.FS(frontendFS)))
log.Println("Servidor rodando em :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
Com Docker multi-stage build, compile o frontend no primeiro estágio, copie para o diretório do Go, e o binário final inclui tudo. O resultado é uma imagem Docker mínima com API e frontend em um único executável.
Próximos Passos
//go:embed transforma seu binário Go em uma unidade autossuficiente de deploy. Combine com Docker multi-stage builds para imagens mínimas, OpenTelemetry para observabilidade, e Kubernetes para orquestração.
Para gerenciar a configuração em runtime (não embutida), veja como usar Cobra e Viper. E para entender como iteradores podem ajudar a percorrer arquivos embutidos de forma mais elegante, confira nosso guia sobre range over func.
Quer ver como outras linguagens resolvem o empacotamento de assets? Veja como Rust usa crates como include_str! e rust-embed para embutir arquivos em tempo de compilação, ou como Zig resolve isso com @embedFile — uma abordagem minimalista que é built-in na linguagem.
FAQ
O que é go:embed e desde quando está disponível?
A diretiva //go:embed foi introduzida no Go 1.16 (fevereiro de 2021). Ela permite embutir arquivos e diretórios no binário compilado usando os tipos string, []byte ou embed.FS, sem dependências externas ou ferramentas de geração de código.
Embutir arquivos aumenta muito o tamanho do binário?
Sim, o tamanho do binário aumenta proporcionalmente ao tamanho dos arquivos embutidos. Para assets de frontend (HTML, CSS, JS minificados), o impacto geralmente é de poucos MB. Para arquivos grandes como vídeos ou datasets, prefira leitura em disco.
Posso embutir arquivos de fora do módulo Go?
Não. A diretiva //go:embed só aceita caminhos relativos ao package atual, e os arquivos devem estar dentro do módulo Go. Não é possível usar caminhos absolutos ou ../ para sair do módulo.
Como embutir arquivos ocultos (dotfiles)?
Por padrão, arquivos começando com . ou _ são ignorados pelo //go:embed. Para incluí-los, use o prefixo all: na diretiva: //go:embed all:templates — isso inclui .gitkeep, _helpers.html e similares.