← Voltar para o blog

GoReleaser em Go: Binários, Checksums e SBOM

Aprenda a fazer releases de binários Go com GoReleaser, checksums, assinatura, SBOM, Docker, CI, rollback e segurança para CLIs e serviços.

GoReleaser em Go resolve um problema que aparece quando o projeto deixa de ser apenas código no GitHub e passa a ser software que outras pessoas instalam, automatizam e rodam em produção. Compilar localmente com go build é suficiente para validar uma ideia. Para distribuir uma CLI, um agent, um worker, um exporter, uma ferramenta de plataforma ou um serviço empacotado em container, você precisa de um processo reproduzível: versão, binários por sistema operacional, checksums, changelog, imagem Docker, assinatura, SBOM e caminho claro de rollback.

Go combina muito bem com esse fluxo porque gera binários estáticos, tem cross-compilation simples e depende pouco de runtime externo. Mas essa vantagem também cria uma armadilha: é fácil mandar um binário por Slack, subir um artefato manual no release ou publicar uma imagem sem saber exatamente qual commit virou produção. Em times brasileiros de backend, SRE, plataforma e ferramentas internas, esse improviso cobra caro quando há incidente, auditoria, vulnerabilidade ou necessidade de reinstalar uma versão antiga.

Este guia mostra como estruturar releases de projetos Go com GoReleaser, checksums, assinatura, SBOM, Docker e CI. Ele complementa os guias de CLI em Go, Docker para Go, TDD e CI/CD em Go, feature flags em Go e graceful shutdown em Go. O objetivo não é empilhar ferramenta, e sim transformar release em um contrato verificável.

Quando GoReleaser faz sentido

GoReleaser é mais útil quando você distribui algo que roda fora do seu ambiente de desenvolvimento. Alguns exemplos comuns:

  1. Uma CLI usada por pessoas desenvolvedoras ou clientes.
  2. Um agent instalado em servidores, Kubernetes ou máquinas de CI.
  3. Um exporter Prometheus ou ferramenta de observabilidade.
  4. Um operador interno que precisa de binários Linux, macOS e Windows.
  5. Um serviço Go publicado como imagem Docker com tag de versão.
  6. Um projeto open source que precisa de releases confiáveis.

Se o projeto é apenas uma API implantada por um pipeline privado, talvez você não precise publicar binários no GitHub Releases. Mesmo assim, os conceitos continuam úteis: versão imutável, artefato rastreável, checksum, imagem com tag de commit, changelog e rollback. GoReleaser pode cuidar da parte de release público, enquanto seu deploy continua em outra ferramenta.

O sinal de maturidade é conseguir responder rapidamente: qual commit gerou o binário 1.4.2? Quais flags de build foram usadas? O checksum confere? A imagem Docker foi construída a partir do mesmo commit? Existe SBOM? Se a versão quebrar, qual é a tag anterior saudável?

Estrutura mínima de um projeto Go

Um projeto simples pode começar assim:

mytool/
  cmd/mytool/main.go
  internal/version/version.go
  go.mod
  .goreleaser.yaml

No pacote de versão, deixe variáveis sobrescritas no build:

package version

var (
    Version = "dev"
    Commit  = "none"
    Date    = "unknown"
)

No comando principal, exponha isso em mytool version ou em uma flag --version. Para CLIs, essa informação ajuda suporte, reprodução de bug e documentação. Para agents e exporters, também vale expor a versão em logs e métricas. Quando alguém abre um chamado dizendo “a ferramenta falhou”, a primeira pergunta não deveria ser um mistério.

Configuração inicial do GoReleaser

Instale localmente apenas para gerar e validar a configuração:

go install github.com/goreleaser/goreleaser/v2@latest
goreleaser init

Depois ajuste o arquivo para seu projeto. Um ponto de partida enxuto:

version: 2

project_name: mytool


before:
  hooks:
    - go mod tidy
    - go test ./...

builds:
  - id: mytool
    main: ./cmd/mytool
    binary: mytool
    env:
      - CGO_ENABLED=0
    goos:
      - linux
      - darwin
      - windows
    goarch:
      - amd64
      - arm64
    ldflags:
      - -s -w
      - -X mytool/internal/version.Version={{.Version}}
      - -X mytool/internal/version.Commit={{.Commit}}
      - -X mytool/internal/version.Date={{.Date}}

archives:
  - id: default
    formats: [tar.gz]
    format_overrides:
      - goos: windows
        formats: [zip]

checksum:
  name_template: "checksums.txt"

snapshot:
  version_template: "{{ incpatch .Version }}-next"

changelog:
  sort: asc

Esse arquivo já faz três coisas importantes: roda testes antes do release, gera binários para múltiplas plataformas e cria um arquivo de checksums. Antes de publicar qualquer coisa, rode:

goreleaser release --snapshot --clean

O modo snapshot não cria release remoto. Ele só monta os artefatos localmente para você inspecionar nomes, arquivos, tamanhos, checksums e mensagens de erro. Isso evita descobrir no pipeline que a configuração está quebrada.

Checksums não são detalhe burocrático

Checksum é uma forma simples de verificar integridade. Se o usuário baixou mytool_1.4.2_linux_amd64.tar.gz, ele pode comparar o hash com checksums.txt. Isso detecta download corrompido, cache errado e substituição acidental de artefato.

O cuidado operacional é tratar o release como imutável. Não publique uma tag, perceba um erro, recompile localmente e sobrescreva o binário com o mesmo número. Isso quebra confiança. Se a versão 1.4.2 saiu errada, publique 1.4.3 corrigindo. O histórico deve contar a verdade.

Em ambientes internos, checksums também ajudam pipelines. Um job pode baixar o binário, validar o checksum e só então instalar. Isso é melhor do que assumir que qualquer arquivo com nome parecido é válido.

Assinatura com cosign

Checksum prova integridade, mas não prova autoria. Para aumentar confiança, assine artefatos ou imagens com cosign. Em times que já usam Sigstore, a assinatura pode entrar no pipeline de release sem depender de chave longa guardada em notebook.

O desenho básico é:

  1. GoReleaser cria binários, archives e checksums.
  2. O pipeline assina o arquivo de checksums ou os artefatos.
  3. A documentação ensina como verificar a assinatura.
  4. O deploy aceita apenas imagem ou binário verificável.

Não comece pelo ritual mais complexo. Comece garantindo que o release é reproduzível e que checksums existem. Depois adicione assinatura onde houver risco real: ferramenta instalada por clientes, binário baixado por curl, agent com permissão sensível, imagem usada em cluster ou projeto open source com usuários externos.

SBOM para dependências Go

SBOM, ou Software Bill of Materials, é uma lista dos componentes usados no artefato. Em Go, isso ajuda a responder perguntas como: qual versão de uma dependência está dentro do binário? Um CVE anunciado hoje afeta quais releases? A imagem publicada contém apenas o binário ou traz pacotes do sistema com vulnerabilidades?

GoReleaser pode ser integrado com ferramentas como Syft para gerar SBOM. Um fluxo comum é gerar CycloneDX ou SPDX junto com os artefatos e anexar ao release. Mesmo que ninguém leia o SBOM no dia a dia, ele vira evidência quando segurança, compliance ou cliente enterprise perguntam o que foi entregue.

Para projetos pequenos, o mínimo é manter go.mod limpo, rodar go mod tidy, revisar dependências novas no pull request e gerar SBOM nos releases importantes. Para projetos que rodam em produção crítica, combine isso com scanners de vulnerabilidade, política de atualização e assinatura.

Imagens Docker com a mesma versão

Muitos projetos Go distribuem duas coisas: binário e imagem Docker. O erro comum é gerar cada uma por um caminho diferente. A CLI recebe uma versão, a imagem recebe outra, e ninguém sabe se v1.4.2 no GitHub é o mesmo código que registry.example.com/mytool:v1.4.2.

GoReleaser pode construir imagens Docker usando o binário gerado. Um exemplo conceitual:

dockers:
  - image_templates:
      - "ghcr.io/example/mytool:{{ .Version }}"
      - "ghcr.io/example/mytool:latest"
    dockerfile: Dockerfile
    use: buildx
    build_flag_templates:
      - "--platform=linux/amd64"

Para multi-arch, use manifest. Para ambientes controlados, evite depender de latest no deploy. latest pode existir para conveniência humana, mas produção deve apontar para tag imutável, digest ou mecanismo equivalente. O rollback precisa ser uma troca explícita de versão, não uma aposta no estado atual do registry.

Pipeline de CI seguro

Um pipeline simples pode seguir esta ordem:

  1. Rodar go test ./... em todo push.
  2. Rodar goreleaser release --snapshot --clean em pull requests relevantes.
  3. Publicar release apenas quando uma tag vX.Y.Z for criada.
  4. Usar secrets do provedor de CI, nunca token local.
  5. Assinar artefatos e imagens quando o projeto exigir.
  6. Registrar link do release no changelog, issue ou deploy log.

O importante é separar validação de publicação. Todo pull request pode testar se a configuração do GoReleaser ainda funciona, mas só uma tag deve publicar artefatos finais. Isso reduz o risco de release acidental e evita poluir o histórico com versões descartáveis.

Também vale proteger o padrão de tags. Se qualquer pessoa ou automação pode criar v9.9.9, o release não é confiável. Em projetos de equipe, defina quem cria tag, como revisão acontece e qual checklist precisa estar verde antes do publish.

Versionamento sem fantasia

Para a maioria dos projetos Go, SemVer é suficiente: MAJOR.MINOR.PATCH. Use PATCH para correções compatíveis, MINOR para recurso novo compatível e MAJOR para quebra de contrato. A parte difícil não é decorar SemVer; é saber qual contrato você oferece.

Em CLI, contrato inclui flags, nomes de subcomandos, formato de saída, exit codes e arquivos de configuração. Em agent, inclui protocolo, variáveis de ambiente, métricas, logs, permissões e comportamento de atualização. Em imagem Docker, inclui porta, usuário, diretório de trabalho, health check e compatibilidade com manifests.

Se um comando muda de saída e scripts de clientes quebram, isso pode ser breaking change mesmo que a API Go interna continue igual. Se uma flag antiga ainda funciona, mas está obsoleta, marque como deprecated antes de remover. A qualidade do release aparece nesses detalhes.

Rollback e incidentes

Release seguro não é só publicar. É conseguir voltar. Antes de anunciar uma versão, tenha resposta para:

  1. Qual versão anterior está saudável?
  2. Onde está o binário ou imagem dessa versão?
  3. O downgrade exige mudança de banco, arquivo ou configuração?
  4. Existe feature flag ou kill switch para reduzir impacto?
  5. Como confirmar que o rollback funcionou?

Esse raciocínio conversa diretamente com feature flags em Go e migrations em Go. O release 1.5.0 pode estar tecnicamente perfeito, mas se ele depende de migration irreversível, o rollback fica mais caro. Em serviços Go, prefira deploys compatíveis: primeiro prepare o schema, depois publique código que entende formato antigo e novo, só então remova caminho antigo.

Para CLIs e agents, o rollback costuma ser reinstalar versão anterior. Isso só é viável se versões antigas continuam disponíveis e documentadas. Não apague releases antigos sem uma política clara.

Erros comuns

O primeiro erro é commitar binário no repositório. Artefato de release pertence a registry, GitHub Releases, bucket ou sistema de pacotes, não ao Git normal. O repositório deve guardar fonte e configuração para reconstruir.

O segundo é gerar versão apenas no nome do arquivo, sem injetar versão no binário. Quando alguém roda mytool --version, precisa ver versão, commit e data. Isso economiza tempo em suporte.

O terceiro é publicar a partir de máquina local. Mesmo que funcione, o processo não é auditável. Use CI com permissões mínimas, logs e secrets controlados.

O quarto é misturar release com deploy automático sem portão. Publicar uma versão e colocá-la em produção podem ser etapas diferentes. Dependendo do risco, você pode publicar o artefato, testar em staging, fazer canary e só então promover.

O quinto é esquecer carreira. Muitas vagas Go DevOps/SRE e vagas Go backend pedem CI/CD, Docker, Kubernetes, segurança, observabilidade, ferramentas internas e ownership de produção. Saber explicar um pipeline de release com checksums, assinatura, SBOM e rollback mostra maturidade além de escrever código. Para comparar como práticas de DevOps aparecem em outras stacks brasileiras, acompanhe também o portal eu.dev.br, que reúne vagas de tecnologia por senioridade e ajuda a enxergar padrões de mercado fora do nicho Go.

Checklist prático

Antes de chamar um release Go de pronto, revise:

  1. go test ./... passa no CI.
  2. goreleaser release --snapshot --clean gera artefatos localmente.
  3. O binário responde --version com versão, commit e data.
  4. checksums.txt é publicado junto dos arquivos.
  5. Imagem Docker usa tag imutável e, quando possível, digest.
  6. SBOM é gerado para releases importantes.
  7. Assinatura existe quando o risco justifica.
  8. Changelog explica mudanças relevantes para usuários.
  9. Versões antigas continuam disponíveis para rollback.
  10. O procedimento de rollback está documentado.

GoReleaser não substitui engenharia de release. Ele automatiza o caminho feliz e remove variação manual. A responsabilidade continua sendo do time: escolher contrato, proteger tags, revisar dependências, testar antes de publicar e observar depois do deploy.

Próximos passos

Se você já tem uma CLI simples, adicione GoReleaser em modo snapshot e valide nomes de artefatos. Depois injete Version, Commit e Date no binário. Em seguida, publique um release real em um repositório de teste, baixe o arquivo, valide o checksum e confirme que --version mostra a tag correta.

Se você trabalha com serviços, comece pela imagem Docker: tag por versão, tag por commit, digest no deploy e changelog claro. Depois evolua para SBOM e assinatura. O objetivo é que cada versão de software Go tenha identidade própria, seja verificável e possa ser substituída com segurança quando produção exigir.