Docker Go e Docker Golang: Deploy de Aplicação
Resposta rápida: Docker Go e Docker Golang são a mesma coisa — fazer deploy de uma aplicação Go com Docker. Use um Dockerfile multi-stage: compile no estágio golang:1.27-alpine com CGO_ENABLED=0 e copie apenas o binário para uma imagem scratch ou distroless. Para desenvolvimento local, use Docker Compose com a API e o PostgreSQL; em produção, execute como usuário sem privilégios, exponha um endpoint de health check e trate SIGTERM para encerrar sem derrubar requisições em andamento.
A seguir você encontra um exemplo completo de Dockerfile para Go, os comandos de build e execução, um compose.yaml com PostgreSQL e o diagnóstico de no such file or directory no build ou na execução do container. A imagem oficial golang fornece o ambiente de compilação; a imagem final contém somente o necessário para executar a aplicação.
Se você ainda não tem a API, comece por API REST com Go e Go para backend. Para o que roda dentro do container em produção, combine com graceful shutdown, health checks (liveness/readiness) e GOMAXPROCS container-aware.
Por que Go + Docker funciona tão bem?
- Binário estático — com
CGO_ENABLED=0, um único executável sem runtime externo - Compilação cruzada nativa —
GOOS=linuxno macOS/Windows sem toolchain extra - Imagens minúsculas —
scratch/ distroless na casa de 5–20 MB - Startup instantâneo — sem JVM, sem interpreter, sem warm-up
- Baixo consumo de memória — ideal para limites de cgroup em Kubernetes
| Stack | Tamanho típico da imagem |
|---|---|
| Go + scratch | ~5–10 MB |
| Go + distroless/static | ~10–20 MB |
| Go + Alpine | ~15–25 MB |
| Node.js + Alpine | ~150–200 MB |
| Java + JRE | ~300–500 MB |
| Python + deps | ~200–400 MB |
No mercado brasileiro, “sabe Go e Docker” quase sempre significa: multi-stage, compose com banco, health endpoint e deploy previsível — não só “sabe rodar docker run”.
Estrutura do projeto
minha-api/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ └── handler/
│ └── handler.go
├── go.mod
├── go.sum # existe quando há dependências que exigem checksums
├── Dockerfile
├── .dockerignore
└── docker-compose.yml
Se estiver começando um projeto novo, inicialize o módulo antes de criar cmd/server/main.go:
go mod init example.com/minha-api
mkdir -p cmd/server
Código mínimo com health HTTP (o que o orquestrador realmente precisa):
// cmd/server/main.go
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
// version é injetada no build via -ldflags
var version = "dev"
type Resposta struct {
Mensagem string `json:"mensagem"`
Timestamp string `json:"timestamp"`
Versao string `json:"versao"`
}
func main() {
porta := os.Getenv("PORT")
if porta == "" {
porta = "8080"
}
mux := http.NewServeMux()
mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(Resposta{
Mensagem: "Olá do Go + Docker!",
Timestamp: time.Now().Format(time.RFC3339),
Versao: version,
})
})
// Health check HTTP — use este path no Docker/K8s, não um flag inventado
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"status":"ok"}`))
})
srv := &http.Server{
Addr: ":" + porta,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
go func() {
log.Printf("servidor na porta %s (versão %s)", porta, version)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
// Graceful shutdown: Docker envia SIGTERM no stop
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Printf("shutdown: %v", err)
}
}
Detalhes de sinal, timeout e draining estão em graceful shutdown em Go. Para liveness vs readiness em Kubernetes, use health checks em Go.
Depois de salvar o código, rode go mod tidy. Este exemplo usa apenas a biblioteca padrão, então pode não gerar go.sum. Nos Dockerfiles abaixo, COPY go.mod go.sum* ./ permite esse caso e também copia go.sum quando presente. Não exclua o arquivo de checksums de projetos que têm dependências; veja a referência de módulos Go.
Dockerfile básico (só para aprender)
# NÃO use isto em produção — ~800 MB com o SDK inteiro
FROM golang:1.27
WORKDIR /app
COPY . .
RUN go mod download
RUN go build -o server ./cmd/server/
EXPOSE 8080
CMD ["./server"]
docker build -t minha-api:basico .
docker run --rm -p 8080:8080 minha-api:basico
curl -s http://localhost:8080/healthz
O problema: a imagem carrega compilador, cache e source. Em CI e produção isso é lento, caro e aumenta a superfície de ataque.
Multi-stage build: a forma certa
# ===========================================
# Estágio 1: compilação
# ===========================================
FROM golang:1.27-alpine AS builder
RUN apk add --no-cache ca-certificates tzdata git
WORKDIR /build
# Dependências primeiro → cache de camada estável
COPY go.mod go.sum* ./
RUN go mod download && go mod verify
COPY . .
ARG APP_VERSION=dev
# Caminho explícito do executável que será copiado para a imagem final
RUN mkdir -p /out && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-trimpath \
-ldflags="-s -w -X main.version=${APP_VERSION}" \
-o /out/server ./cmd/server/
# ===========================================
# Estágio 2: produção mínima
# ===========================================
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /out/server /server
EXPOSE 8080
USER 65534:65534
ENTRYPOINT ["/server"]
docker build --build-arg APP_VERSION=1.2.3 -t minha-api:prod .
docker images minha-api
# minha-api prod ~8 MB
# minha-api basico ~800 MB
Flags que importam
| Flag / escolha | Para que serve |
|---|---|
CGO_ENABLED=0 | Binário 100% estático (obrigatório em scratch) |
GOOS=linux | Target Linux mesmo no macOS/Windows |
-ldflags="-s -w" | Remove símbolos/debug (~20–30% menor) |
-X main.version=… | Injeta versão no binário |
-trimpath | Paths reproduzíveis no binário |
scratch | Base vazia; menor superfície |
Alternativa distroless (recomendado em muitos times)
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /out/server /server
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]
Distroless já inclui CA certs em variantes comuns, roda como nonroot e ainda fica pequena. Prefira distroless se o time precisa de um meio-termo entre scratch e Alpine.
HEALTHCHECK: não quebre no scratch
A diretiva HEALTHCHECK do Dockerfile precisa de um binário dentro da imagem. Em scratch não há wget/curl, e um CMD ["/server", "-health"] só funciona se o seu binário implementar essa flag.
Padrões que funcionam:
- Orquestrador faz o probe HTTP (Kubernetes
httpGetem/healthz, Cloud Run, ECS) — preferível - Alpine/distroless com ferramenta mínima só se você realmente precisa de
dockerhealth local - Compose com
healthcheckusando a imagem que tem shell, ou probe externo
O /server deste exemplo não implementa um subcomando de health check. Não o execute como teste de saúde: isso iniciaria outro servidor em vez de verificar o processo existente. Consulte a referência de HEALTHCHECK e configure um probe HTTP externo para /healthz quando usar esta imagem scratch.
Como corrigir no such file or directory no Docker Go
Primeiro identifique a etapa: falha em RUN go build, em COPY ou ao iniciar /server são problemas diferentes. Leia o caminho completo citado no log; a mensagem isolada não prova que falta /out nem que o problema é cgo.
-o /out é um arquivo ou um diretório?
Segundo a documentação de go build -o, se a saída é um diretório existente ou termina em /, os executáveis são escritos nesse diretório. Para um único pacote main, se /out não existe e você usa -o /out, o resultado pode ser um arquivo chamado /out, não /out/server.
Para evitar ambiguidade, mantenha o mesmo caminho no build e no COPY:
WORKDIR /build
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /out/server ./cmd/server/
RUN test -f /out/server && test -x /out/server
# No estágio final:
# COPY --from=builder /out/server /server
# ENTRYPOINT ["/server"]
Verificado com Go 1.27.1: o comando acima cria diretórios pais ausentes durante a gravação do executável. mkdir -p /out pode deixar a intenção explícita, mas não é uma solução geral para esse erro. Se /out já for um arquivo de uma compilação anterior, /out/server não será um caminho válido: escolha uma saída consistente.
Diagnóstico por etapa
| Etapa que falha | O que verificar | Ação |
|---|---|---|
COPY go.mod go.sum ... | O exemplo só usa biblioteca padrão e não tem go.sum? | Use COPY go.mod go.sum* ./; preserve os checksums quando houver dependências. |
RUN go build ... ./cmd/server/ | O pacote existe dentro do builder? O contexto contém o código? | Confira WORKDIR, COPY . ., .dockerignore e o diretório usado em docker build. |
COPY --from=builder /out/server /server | O build gerou /out, /out/server ou outro nome? | Alinhe -o e COPY; confirme o artefato com test -f no builder. |
Inicialização de /server | O arquivo foi copiado? O executável requer um loader ausente? | Confira ENTRYPOINT; inspecione o binário e use uma base compatível com suas dependências. |
Rode o build com logs detalhados e, se necessário, pare no estágio de compilação:
docker build --progress=plain --no-cache -t minha-api:debug .
docker build --target builder -t minha-api:builder .
docker run --rm --entrypoint /bin/sh minha-api:builder \
-c 'pwd; ls -l /out/server; test -x /out/server'
Esses comandos de inspeção usam o shell do builder Alpine, não da imagem scratch. A documentação de multi-stage do Docker explica --target e a cópia entre estágios.
O binário existe, mas o container não inicia
No Linux, execve pode retornar ENOENT quando o arquivo existe, mas seu interpretador ELF (loader) não existe; veja execve(2). Isso pode acontecer ao copiar um binário dinamicamente ligado para scratch.
Para o exemplo sem dependências C deste guia, recompile com CGO_ENABLED=0. Se sua aplicação depende de cgo, não desative a funcionalidade às cegas: use uma imagem com loader e bibliotecas compatíveis e confira as dependências com readelf -l e readelf -d em um ambiente que tenha essas ferramentas. A documentação de cgo descreve quando ele é habilitado e como controlá-lo.
Também alinhe a arquitetura do binário à do destino. Os Dockerfiles deste guia fixam GOARCH=amd64; não publique esse artefato como linux/arm64. Uma arquitetura incompatível costuma produzir exec format error, não o mesmo diagnóstico de loader ausente.
.dockerignore
.git
.github
.vscode
.idea
*.md
docs/
Dockerfile*
docker-compose*.yml
.dockerignore
.env
.env.*
bin/
dist/
coverage.out
*.test
*.prof
Sem isso, o contexto de build engorda e o cache fica instável.
Docker Compose: Go + PostgreSQL
# docker-compose.yml
services:
api:
build:
context: .
dockerfile: Dockerfile
args:
APP_VERSION: "dev"
ports:
- "8080:8080"
environment:
PORT: "8080"
DATABASE_URL: postgres://app:app@postgres:5432/minha_api?sslmode=disable
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: minha_api
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d minha_api"]
interval: 5s
timeout: 5s
retries: 10
volumes:
pgdata:
docker compose up -d --build
docker compose logs -f api
curl -s http://localhost:8080/healthz
docker compose down -v
Para o lado SQL da aplicação, veja Go com PostgreSQL e database/sql + pool em produção. Configuração via env/Viper: configuração com Viper.
Dev com hot reload (Air)
# Dockerfile.dev
FROM golang:1.27-alpine
RUN go install github.com/air-verse/air@latest
WORKDIR /app
COPY go.mod go.sum* ./
RUN go mod download
COPY . .
EXPOSE 8080
CMD ["air", "-c", ".air.toml"]
# docker-compose.override.yml
services:
api:
build:
dockerfile: Dockerfile.dev
volumes:
- .:/app
- gomod:/go/pkg/mod
volumes:
gomod:
Produção não monta o source nem usa Air. O artefato de prod é o multi-stage imutável.
Configuração e 12-factor
Containers esperam config por ambiente, não por arquivo bakeado:
porta := os.Getenv("PORT")
dsn := os.Getenv("DATABASE_URL")
Defaults no código ou YAML versionado; segredos e URLs por env/secret do orquestrador. O mesmo binário sobe em dev, staging e prod. Isso é o que conecta Docker a Kubernetes com Go e a releases com GoReleaser (binário + imagem no mesmo pipeline).
Gotchas que quebram deploy
1. Esqueceu CGO_ENABLED=0
standard_init_linux.go: exec user process caused: no such file or directory
Solução: CGO_ENABLED=0 ou use imagem com libc compatível (não scratch).
2. Timezone ausente no scratch
loc, err := time.LoadLocation("America/Sao_Paulo")
// err: unknown time zone America/Sao_Paulo
Solução: copiar /usr/share/zoneinfo ou import _ "time/tzdata".
3. HTTPS externo sem CA certs
x509: certificate signed by unknown authority
Solução: copiar ca-certificates.crt no estágio final (ou distroless).
4. GOMAXPROCS e cgroup
Container com 500m CPU em nó de 16 cores, em Go antigo, usava 16 P. Desde o Go 1.25 o runtime é container-aware. Detalhes: GOMAXPROCS container-aware.
5. Testes de integração sem Docker flaky
Subir Postgres “na mão” no CI quebra. Use Testcontainers em Go para subir dependências reais no teste e derrubar ao final.
6. SIGTERM ignorado
docker stop manda SIGTERM e depois SIGKILL. Sem graceful shutdown, requests em voo morrem no meio do deploy.
Dockerfile de produção (checklist)
ARG GO_VERSION=1.27
ARG APP_VERSION=dev
FROM golang:${GO_VERSION}-alpine AS builder
RUN apk add --no-cache ca-certificates tzdata git
WORKDIR /build
COPY go.mod go.sum* ./
RUN go mod download && go mod verify
COPY . .
ARG APP_VERSION
RUN mkdir -p /out && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-trimpath \
-ldflags="-s -w -X main.version=${APP_VERSION}" \
-o /out/server ./cmd/server/
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /out/server /server
EXPOSE 8080
USER 65534:65534
ENTRYPOINT ["/server"]
Checklist rápido antes do push:
- Multi-stage (builder ≠ runtime)
-
CGO_ENABLED=0se base for scratch/distroless - CA certs + timezone (ou
time/tzdata) -
USERnão-root -
/healthz(ou liveness/readiness separados) - Timeouts no
http.Server - Graceful shutdown em SIGTERM
-
.dockerignoreenxuto - Versão injetada no binário
- Secrets só por env/secret manager
Comandos essenciais
docker build --build-arg APP_VERSION=$(git rev-parse --short HEAD) -t minha-api:latest .
docker run --rm -p 8080:8080 -e PORT=8080 minha-api:latest
docker compose up -d --build
docker compose logs -f api
docker history minha-api:latest
docker image prune -f
Trilha de produção (próximos passos)
| Objetivo | Página |
|---|---|
| API HTTP completa | API REST com Go |
| Backend no Brasil | Go para backend |
| Banco | Go com PostgreSQL |
| Shutdown limpo | Graceful shutdown |
| Probes K8s | Health checks |
| CPU em cgroup | GOMAXPROCS |
| Testes com containers | Testcontainers |
| Release + imagem | GoReleaser |
| Orquestração | Go no Kubernetes |
| Vagas | Vagas DevOps · Vagas backend |
Conclusão
Docker Go / Docker Golang em 2026 não é “colocar o SDK na imagem”. É multi-stage, binário estático, base mínima, health HTTP, config por env e shutdown educado. Com isso você entrega imagens de menos de 15 MB, deploys previsíveis e um portfólio alinhado ao que fintechs e times de plataforma pedem no Brasil.
Próximo passo prático: containerize a API do tutorial de API REST, suba Postgres no compose e adicione testes com Testcontainers. Depois, publique binário e imagem com GoReleaser.