Artigo 49 — Compilação cruzada, binários estáticos e Docker com Go
Curso: Dominando Go em 1 Ano Prof. Ricardo Matos Módulo 9 — Deploy, Cloud e Carreira
O binário como unidade de deploy
Uma das vantagens mais práticas do Go é sua capacidade de produzir binários completamente autocontidos. Um binário Go não precisa de runtime instalado, não depende de bibliotecas compartilhadas do sistema e pode ser compilado para qualquer plataforma a partir de qualquer outra. Essa característica transforma o deploy em algo fundamentalmente simples: copie o arquivo, execute.
Docker amplifica essa vantagem. Com builds multistage, a imagem final contém apenas o binário e suas dependências mínimas de sistema — tipicamente menos de 20 MB, contra centenas de MB de uma imagem com runtime Java ou Python.
Compilação básica
# Compilar para a plataforma atual
go build -o bin/app ./cmd/api
# Com otimizações para produção
go build -ldflags="-w -s" -o bin/app ./cmd/api
As flags -w e -s removem informações de debug e a tabela de símbolos, reduzindo o tamanho do binário em 30-40% sem impacto na funcionalidade.
Embutindo metadados via ldflags
// cmd/api/main.go
package main
import (
"fmt"
"os"
)
// Variáveis preenchidas em tempo de build via -ldflags
var (
Versao = "desenvolvimento"
Commit = "desconhecido"
BuildTime = "desconhecido"
GoVersion = "desconhecido"
)
func exibirVersao() {
fmt.Printf("Versão: %s\n", Versao)
fmt.Printf("Commit: %s\n", Commit)
fmt.Printf("Build: %s\n", BuildTime)
fmt.Printf("Go: %s\n", GoVersion)
}
func main() {
if len(os.Args) > 1 && os.Args[1] == "version" {
exibirVersao()
return
}
// ... resto da aplicação
}
# Build com metadados
go build \
-ldflags="-w -s \
-X main.Versao=1.2.3 \
-X main.Commit=$(git rev-parse --short HEAD) \
-X main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-X main.GoVersion=$(go version | awk '{print $3}')" \
-o bin/app ./cmd/api
# Verificando
./bin/app version
# Versão: 1.2.3
# Commit: a1b2c3d
# Build: 2024-03-15T10:30:00Z
# Go: go1.22.0
Compilação cruzada
Go suporta compilação cruzada nativamente — sem ferramentas externas, sem containers, sem configurações complexas. As variáveis GOOS e GOARCH controlam o alvo:
# Linux AMD64 (servidores na nuvem)
GOOS=linux GOARCH=amd64 go build -o bin/app-linux-amd64 ./cmd/api
# Linux ARM64 (AWS Graviton, Raspberry Pi 4)
GOOS=linux GOARCH=arm64 go build -o bin/app-linux-arm64 ./cmd/api
# macOS Intel
GOOS=darwin GOARCH=amd64 go build -o bin/app-darwin-amd64 ./cmd/api
# macOS Apple Silicon
GOOS=darwin GOARCH=arm64 go build -o bin/app-darwin-arm64 ./cmd/api
# Windows 64-bit
GOOS=windows GOARCH=amd64 go build -o bin/app-windows-amd64.exe ./cmd/api
# Windows ARM64
GOOS=windows GOARCH=arm64 go build -o bin/app-windows-arm64.exe ./cmd/api
Plataformas suportadas:
# Lista todas as combinações GOOS/GOARCH suportadas
go tool dist list
Script de release multi-plataforma
#!/bin/bash
# scripts/release.sh
APP="taskapi"
VERSAO="${1:-dev}"
COMMIT=$(git rev-parse --short HEAD)
BUILD=$(date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS="-w -s -X main.Versao=${VERSAO} -X main.Commit=${COMMIT} -X main.BuildTime=${BUILD}"
PLATAFORMAS=(
"linux/amd64"
"linux/arm64"
"darwin/amd64"
"darwin/arm64"
"windows/amd64"
)
mkdir -p dist
for PLATAFORMA in "${PLATAFORMAS[@]}"; do
OS="${PLATAFORMA%/*}"
ARCH="${PLATAFORMA#*/}"
NOME="${APP}-${VERSAO}-${OS}-${ARCH}"
EXT=""
if [ "$OS" = "windows" ]; then
EXT=".exe"
fi
echo "Compilando ${NOME}${EXT}..."
GOOS="$OS" GOARCH="$ARCH" go build \
-ldflags="$LDFLAGS" \
-trimpath \
-o "dist/${NOME}${EXT}" \
./cmd/api
# Comprime com tar/zip
if [ "$OS" = "windows" ]; then
zip -j "dist/${NOME}.zip" "dist/${NOME}${EXT}"
else
tar -czf "dist/${NOME}.tar.gz" -C dist "${NOME}"
fi
# Remove binário não comprimido
rm "dist/${NOME}${EXT}"
done
echo "Binários gerados em dist/"
ls -lh dist/
Binários estáticos: CGo e suas implicações
Por padrão, Go produz binários que dependem da libc do sistema para algumas operações — DNS e usuários do sistema em particular. Para binários verdadeiramente estáticos:
# Desabilita CGo completamente
CGO_ENABLED=0 go build -o bin/app ./cmd/api
# Verifica se o binário tem dependências dinâmicas
ldd bin/app
# ldd: bin/app: não é um executável dinâmico ← binário estático
Com CGO_ENABLED=0, o Go usa implementações puras em Go para DNS e outras operações. Isso é essencial para:
- Imagens Docker baseadas em
scratchoualpine - Deploy em ambientes sem libc compatível
- Garantia de portabilidade máxima
Atenção com drivers de banco de dados CGo. O driver SQLite mattn/go-sqlite3 usa CGo. Com CGO_ENABLED=0, ele não compila. Use modernc.org/sqlite (puro Go) como alternativa.
Dockerfile multistage: do básico ao otimizado
Versão básica
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-w -s" \
-o taskapi ./cmd/api
FROM alpine:3.19
COPY --from=builder /app/taskapi /usr/local/bin/taskapi
EXPOSE 8080
ENTRYPOINT ["taskapi"]
Versão otimizada com cache de layers
# syntax=docker/dockerfile:1
FROM golang:1.22-alpine AS deps
WORKDIR /app
# Cache separado para dependências — só rebuilda se go.mod/go.sum mudarem
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
FROM deps AS builder
ARG VERSAO=dev
ARG COMMIT=desconhecido
ARG BUILD_TIME=desconhecido
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build \
-ldflags="-w -s \
-X main.Versao=${VERSAO} \
-X main.Commit=${COMMIT} \
-X main.BuildTime=${BUILD_TIME}" \
-trimpath \
-o /app/taskapi \
./cmd/api
# Imagem de produção
FROM gcr.io/distroless/static-debian12 AS producao
# Metadados da imagem
LABEL org.opencontainers.image.title="Task API"
LABEL org.opencontainers.image.version="${VERSAO}"
LABEL org.opencontainers.image.source="https://github.com/usuario/taskapi"
# Certificados TLS já incluídos na imagem distroless
COPY --from=builder /app/taskapi /taskapi
# Não executar como root
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/taskapi"]
A imagem distroless/static contém apenas:
- Certificados CA para HTTPS
- Arquivos de timezone
- Usuário nonroot
Sem shell, sem gerenciador de pacotes, sem utilidades Unix. Menor superfície de ataque possível.
Comparativo de tamanhos de imagem
# Construindo variantes
docker build --target producao -t taskapi:distroless .
docker build -f Dockerfile.alpine -t taskapi:alpine .
# Comparando tamanhos
docker images taskapi
# REPOSITORY TAG SIZE
# taskapi distroless 12.8MB
# taskapi alpine 18.2MB
# taskapi ubuntu 82.5MB ← sem multistage
Build com GitHub Actions
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-binarios:
name: Build Binários
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Build multi-plataforma
run: |
make release VERSAO=${{ github.ref_name }}
- name: Upload artefatos
uses: actions/upload-artifact@v4
with:
name: binarios
path: dist/
build-docker:
name: Build e Push Docker
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login no GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Metadados da imagem
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=tag
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build e push multi-arch
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSAO=${{ github.ref_name }}
COMMIT=${{ github.sha }}
BUILD_TIME=${{ github.event.head_commit.timestamp }}
cache-from: type=gha
cache-to: type=gha,mode=max
Build multi-arquitetura com buildx
# Criar builder com suporte multi-arch
docker buildx create --name multiarch --use
docker buildx inspect --bootstrap
# Build para múltiplas arquiteturas simultaneamente
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg VERSAO=1.2.3 \
-t ghcr.io/usuario/taskapi:1.2.3 \
-t ghcr.io/usuario/taskapi:latest \
--push \
.
# Verificar manifests
docker buildx imagetools inspect ghcr.io/usuario/taskapi:1.2.3
Embed: arquivos estáticos no binário
O pacote embed permite incluir arquivos estáticos diretamente no binário:
package main
import (
"embed"
"io/fs"
"net/http"
)
//go:embed migrations/*.sql
var migrationFiles embed.FS
//go:embed web/dist
var staticFiles embed.FS
//go:embed config/defaults.yaml
var configPadrao []byte
func main() {
// Acessa as migrations embutidas
entries, _ := migrationFiles.ReadDir("migrations")
for _, e := range entries {
conteudo, _ := migrationFiles.ReadFile("migrations/" + e.Name())
fmt.Println(e.Name(), len(conteudo), "bytes")
}
// Serve arquivos estáticos do frontend
webFS, _ := fs.Sub(staticFiles, "web/dist")
http.Handle("/", http.FileServer(http.FS(webFS)))
// Usa configuração padrão embutida
fmt.Println("Config padrão:", string(configPadrao))
}
Com embed, o deploy é um único arquivo — sem diretórios extras, sem preocupação com caminhos relativos.
Otimizações adicionais de build
# -trimpath remove caminhos do sistema de build do binário
# (importante para builds reproduzíveis e privacidade)
go build -trimpath ./cmd/api
# Gzip no binário para distribuição
gzip -9 -k bin/taskapi
ls -lh bin/
# bin/taskapi 12.8 MB
# bin/taskapi.gz 4.2 MB
# UPX para compressão com decompressão automática
# (cuidado: pode ser detectado como suspeito por antivírus)
upx --best bin/taskapi
ls -lh bin/
# bin/taskapi 4.8 MB
# Build com PGO (Profile-Guided Optimization) — Go 1.21+
# Coleta perfil de CPU de produção, usa para otimizar o próximo build
go build -pgo=cpu.pprof -o bin/taskapi-pgo ./cmd/api
Makefile completo de build
APP := taskapi
VERSAO := $(shell git describe --tags --always --dirty)
COMMIT := $(shell git rev-parse --short HEAD)
BUILD := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -ldflags "-w -s -X main.Versao=$(VERSAO) -X main.Commit=$(COMMIT) -X main.BuildTime=$(BUILD)"
.PHONY: build build-linux build-linux-arm build-all docker release clean
build:
CGO_ENABLED=0 go build $(LDFLAGS) -trimpath -o bin/$(APP) ./cmd/api
build-linux:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -trimpath -o bin/$(APP)-linux-amd64 ./cmd/api
build-linux-arm:
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build $(LDFLAGS) -trimpath -o bin/$(APP)-linux-arm64 ./cmd/api
build-all: build-linux build-linux-arm
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -trimpath -o bin/$(APP)-darwin-amd64 ./cmd/api
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -trimpath -o bin/$(APP)-darwin-arm64 ./cmd/api
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build $(LDFLAGS) -trimpath -o bin/$(APP)-windows-amd64.exe ./cmd/api
docker:
docker build \
--build-arg VERSAO=$(VERSAO) \
--build-arg COMMIT=$(COMMIT) \
--build-arg BUILD_TIME=$(BUILD) \
-t $(APP):$(VERSAO) \
-t $(APP):latest \
.
release: build-all docker
@echo "Release $(VERSAO) pronto"
clean:
rm -rf bin/ dist/
Resumo do que foi coberto
Este artigo apresentou compilação e Docker em Go de forma completa: flags de build para otimização e metadados embutidos via ldflags, compilação cruzada para todas as plataformas com GOOS e GOARCH, script de release multi-plataforma, binários estáticos com CGO_ENABLED=0, Dockerfile multistage com cache de layers e imagem distroless, comparativo de tamanhos de imagem, pipeline de CI com GitHub Actions para build multi-arch, embed para incluir arquivos estáticos no binário e Makefile completo de build. O próximo artigo explora deploy na nuvem com Railway, Fly.io e Google Cloud Run.
Referências e leituras complementares
-
Documentação go build — Referência de todas as flags de compilação. https://pkg.go.dev/cmd/go#hdr-Compile_packages_and_dependencies
-
distroless images — Imagens minimalistas do Google para produção. https://github.com/GoogleContainerTools/distroless
-
Docker multistage builds — Guia oficial de builds multistage. https://docs.docker.com/build/building/multi-stage/
-
Go embed — Documentação do pacote embed. https://pkg.go.dev/embed
-
Profile-Guided Optimization — PGO em Go 1.21+. https://go.dev/doc/pgo
-
Docker buildx — Build multi-plataforma com buildx. https://docs.docker.com/buildx/working-with-buildx/
Próximo artigo: Artigo 50 — Go na nuvem: deploy no Railway, Fly.io e Google Cloud Run**
you asked
Sim