O que é Embed em Go?
O embed é um recurso introduzido no Go 1.16 que permite incorporar arquivos e diretórios diretamente no binário compilado do seu programa. Antes desse recurso, desenvolvedores Go precisavam recorrer a ferramentas externas como go-bindata ou packr para embutir assets estáticos — agora isso é suportado nativamente pela linguagem através do package embed e da diretiva especial //go:embed.
A grande vantagem do embed é a simplificação do deploy: em vez de distribuir um binário junto com arquivos de configuração, templates HTML, arquivos CSS, JavaScript ou migrações de banco de dados, você distribui um único binário que contém tudo. Isso é especialmente valioso em ambientes de containers e microsserviços, onde imagens Docker menores e mais simples são preferíveis.
O embed funciona em tempo de compilação — o compilador lê os arquivos especificados e os inclui como dados no binário final. Em tempo de execução, esses dados ficam disponíveis como se fossem arquivos em disco, mas sem depender do sistema de arquivos real.
Como usar a diretiva //go:embed
A diretiva //go:embed é um comentário especial que instrui o compilador a incorporar um ou mais arquivos em uma variável. Ela deve aparecer imediatamente antes da declaração da variável e requer a importação do package embed.
Incorporando um único arquivo como string
O caso mais simples é embutir o conteúdo de um arquivo em uma variável do type string:
package main
import (
_ "embed"
"fmt"
)
//go:embed version.txt
var version string
func main() {
fmt.Printf("Versão: %s\n", version)
}
Nesse exemplo, o conteúdo do arquivo version.txt será incorporado na variável version durante a compilação. Se o arquivo não existir, o build falhará com um error claro.
Incorporando como []byte
Para arquivos binários (imagens, PDFs, fontes), use []byte:
import _ "embed"
//go:embed logo.png
var logoPNG []byte
Isso é útil quando você precisa servir o arquivo como resposta HTTP ou processá-lo em memória.
Incorporando múltiplos arquivos com embed.FS
O type embed.FS permite incorporar múltiplos arquivos e diretórios inteiros, implementando a interface fs.FS da biblioteca padrão:
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed templates/*
var templatesFS embed.FS
//go:embed static/css/* static/js/* static/images/*
var staticFS embed.FS
func main() {
// Listar arquivos incorporados
entries, err := fs.ReadDir(templatesFS, "templates")
if err != nil {
fmt.Println("Erro:", err)
return
}
for _, entry := range entries {
fmt.Println("Template:", entry.Name())
}
}
O pattern * inclui todos os arquivos do diretório (exceto arquivos que começam com . ou _). Para incluir esses arquivos ocultos, use o pattern all::
//go:embed all:templates
var templatesFS embed.FS
Embed em servidores web
Um dos casos de uso mais poderosos é embutir assets estáticos em servidores HTTP. Combinado com http.FS, você cria um servidor web completamente autocontido:
package main
import (
"embed"
"io/fs"
"log"
"net/http"
)
//go:embed static/*
var staticFiles embed.FS
//go:embed templates/*
var templateFiles embed.FS
func main() {
// Servir arquivos estáticos removendo o prefixo "static"
staticSub, err := fs.Sub(staticFiles, "static")
if err != nil {
log.Fatal(err)
}
http.Handle("/static/", http.StripPrefix("/static/",
http.FileServer(http.FS(staticSub))))
// Rota principal
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
data, _ := templateFiles.ReadFile("templates/index.html")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(data)
})
log.Println("Servidor rodando em :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Essa abordagem é ideal para criar servidores web que podem ser distribuídos como um único binário, facilitando deploys em Docker e plataformas cloud.
Embed com templates HTML
Outro padrão extremamente comum é embutir templates HTML usando o package html/template:
package main
import (
"embed"
"html/template"
"log"
"net/http"
)
//go:embed templates/*.html
var templateFS embed.FS
var tmpl *template.Template
func init() {
var err error
tmpl, err = template.ParseFS(templateFS, "templates/*.html")
if err != nil {
log.Fatal("Erro ao parsear templates:", err)
}
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
dados := map[string]string{
"Titulo": "Minha Aplicação Go",
"Mensagem": "Olá, mundo!",
}
tmpl.ExecuteTemplate(w, "index.html", dados)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
A func template.ParseFS aceita qualquer fs.FS, tornando a integração com embed transparente.
Embed para migrações de banco de dados
Embutir arquivos de migração SQL é uma prática adotada por bibliotecas populares como golang-migrate e goose:
import "embed"
//go:embed migrations/*.sql
var migrationsFS embed.FS
func runMigrations(db *sql.DB) error {
entries, err := fs.ReadDir(migrationsFS, "migrations")
if err != nil {
return err
}
for _, entry := range entries {
content, err := migrationsFS.ReadFile("migrations/" + entry.Name())
if err != nil {
return err
}
_, err = db.Exec(string(content))
if err != nil {
return fmt.Errorf("migração %s falhou: %w", entry.Name(), err)
}
}
return nil
}
Isso garante que as migrações estão sempre sincronizadas com a versão do binário — eliminando o risco de deploy sem os arquivos de migração corretos.
Regras e limitações do embed
Existem regras importantes que você precisa conhecer ao usar embed:
A diretiva deve preceder imediatamente a variável — sem linhas em branco entre o comentário
//go:embede a declaraçãovar.Tipos permitidos: apenas
string,[]byteeembed.FS. Outros tipos causam erro de compilação.Caminhos relativos ao pacote: os caminhos na diretiva são relativos ao diretório do package que contém o arquivo fonte.
Sem
..nos caminhos: não é permitido referenciar arquivos fora do diretório do module.Variáveis devem ser de nível de pacote: não é possível usar
//go:embedem variáveis locais de func.Arquivos ocultos: por padrão, arquivos começando com
.ou_são excluídos. Use o prefixoall:para incluí-los.
// Isso NÃO funciona — variável local
func exemplo() {
//go:embed config.json
var cfg string // erro de compilação
}
Boas práticas com embed
Para usar embed de forma eficiente em projetos reais:
- Organize os arquivos em diretórios dedicados —
static/,templates/,migrations/— para manter o código limpo - Use
fs.Subpara remover prefixos quando servir arquivos via HTTP - Prefira
embed.FSsobre string/[]byte quando trabalhar com múltiplos arquivos - Cuidado com o tamanho do binário — embutir muitos assets grandes pode aumentar significativamente o tamanho do executável
- Combine com go test para testar o conteúdo embutido em ambiente de CI/CD
- Considere benchmarks para comparar a performance de leitura de embed vs disco
O embed é uma ferramenta essencial para quem desenvolve aplicações Go modernas, especialmente em arquiteturas de microsserviços e deploys containerizados.
Perguntas frequentes sobre Embed em Go
O embed aumenta o tamanho do binário?
Sim, todo conteúdo embutido é incluído no binário final. Se você embutir 10 MB de assets estáticos, o binário crescerá aproximadamente 10 MB. Por isso é importante considerar o que realmente precisa ser embutido. Para assets muito grandes, considere utilizar um CDN ou sistema de armazenamento externo.
Posso modificar arquivos embutidos em tempo de execução?
Não. Arquivos embutidos com //go:embed são somente leitura. O embed.FS implementa a interface fs.ReadFileFS e fs.ReadDirFS, mas não possui métodos de escrita. Se você precisa modificar conteúdo, copie os dados para uma variável mutável ou escreva em disco.
Qual a diferença entre embed.FS e os.ReadFile?
O embed.FS lê dados que já estão em memória como parte do binário — não acessa o disco. Já os.ReadFile faz uma leitura do sistema de arquivos real. A vantagem do embed é que não há dependência de arquivos externos em tempo de execução. A desvantagem é que mudanças nos arquivos requerem recompilação.
Embed funciona com go test?
Sim, o embed funciona perfeitamente em arquivos _test.go. Isso é útil para embutir fixtures de teste, arquivos JSON de referência ou qualquer dado necessário para validação. É uma alternativa mais elegante ao diretório testdata/, embora ambas as abordagens coexistam bem.