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:

  1. A diretiva deve preceder imediatamente a variável — sem linhas em branco entre o comentário //go:embed e a declaração var.

  2. Tipos permitidos: apenas string, []byte e embed.FS. Outros tipos causam erro de compilação.

  3. Caminhos relativos ao pacote: os caminhos na diretiva são relativos ao diretório do package que contém o arquivo fonte.

  4. Sem .. nos caminhos: não é permitido referenciar arquivos fora do diretório do module.

  5. Variáveis devem ser de nível de pacote: não é possível usar //go:embed em variáveis locais de func.

  6. Arquivos ocultos: por padrão, arquivos começando com . ou _ são excluídos. Use o prefixo all: 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 dedicadosstatic/, templates/, migrations/ — para manter o código limpo
  • Use fs.Sub para remover prefixos quando servir arquivos via HTTP
  • Prefira embed.FS sobre 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.