Janeiro 2026 · ~11 min

Docker Go e Docker Golang: Deploy de Aplicação

Faça deploy de Go com Docker: Dockerfile multi-stage, Compose e diagnóstico de no such file or directory no build, COPY ou ao executar o container.

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?

  1. Binário estático — com CGO_ENABLED=0, um único executável sem runtime externo
  2. Compilação cruzada nativa — GOOS=linux no macOS/Windows sem toolchain extra
  3. Imagens minúsculas — scratch / distroless na casa de 5–20 MB
  4. Startup instantâneo — sem JVM, sem interpreter, sem warm-up
  5. Baixo consumo de memória — ideal para limites de cgroup em Kubernetes
StackTamanho 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 / escolhaPara que serve
CGO_ENABLED=0Binário 100% estático (obrigatório em scratch)
GOOS=linuxTarget Linux mesmo no macOS/Windows
-ldflags="-s -w"Remove símbolos/debug (~20–30% menor)
-X main.version=…Injeta versão no binário
-trimpathPaths reproduzíveis no binário
scratchBase 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:

  1. Orquestrador faz o probe HTTP (Kubernetes httpGet em /healthz, Cloud Run, ECS) — preferível
  2. Alpine/distroless com ferramenta mínima só se você realmente precisa de docker health local
  3. Compose com healthcheck usando 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 falhaO que verificarAçã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 /serverO build gerou /out, /out/server ou outro nome?Alinhe -o e COPY; confirme o artefato com test -f no builder.
Inicialização de /serverO 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=0 se base for scratch/distroless
  • CA certs + timezone (ou time/tzdata)
  • USER não-root
  • /healthz (ou liveness/readiness separados)
  • Timeouts no http.Server
  • Graceful shutdown em SIGTERM
  • .dockerignore enxuto
  • 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)

ObjetivoPágina
API HTTP completaAPI REST com Go
Backend no BrasilGo para backend
BancoGo com PostgreSQL
Shutdown limpoGraceful shutdown
Probes K8sHealth checks
CPU em cgroupGOMAXPROCS
Testes com containersTestcontainers
Release + imagemGoReleaser
OrquestraçãoGo no Kubernetes
VagasVagas 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.

Perguntas frequentes

Como fazer Dockerfile multi-stage para Go?

Use um estágio builder com golang:*-alpine para compilar com CGO_ENABLED=0 e um estágio final mínimo (scratch, distroless ou alpine). Copie só o binário, certificados CA e timezone data. O resultado típico fica entre 5 e 20 MB, em vez de ~800 MB de uma imagem com o SDK completo.

Qual a melhor imagem base para Go em produção?

scratch ou gcr.io/distroless/static-debian12 são as escolhas mais comuns em 2026. scratch é a menor; distroless facilita debugging e já traz CA certs. Alpine serve quando você precisa de shell ou de ferramentas no container. Evite golang:latest em produção — ela carrega o compilador inteiro.

Quando usar CGO_ENABLED=0 no Docker com scratch?

Para o exemplo deste guia, CGO_ENABLED=0 compila sem cgo e evita depender de libc no scratch. Ter cgo habilitado não significa que todo binário será dinâmico: confira as dependências do executável. Se a aplicação exige bibliotecas C, use uma imagem com as bibliotecas e o loader compatíveis ou configure uma compilação estática adequada.

Como usar docker-compose com Go e PostgreSQL?

Defina serviços api e postgres, passe DATABASE_URL por environment, use depends_on com condition: service_healthy e healthcheck no Postgres (pg_isready). Em dev, monte o código com volume e use Air para hot reload. Em prod, construa multi-stage e não monte o source.

Go precisa de Docker para deploy?

Não. Go gera binário único e pode rodar em VM, bare metal ou systemd. Docker (e Kubernetes) viraram o padrão de entrega porque padronizam env, rede, health check e escala. Se a vaga pede Go + Docker, o multi-stage build e o compose com banco são o mínimo esperado no portfólio.

Como o Go se comporta em containers (GOMAXPROCS)?

Desde o Go 1.25 o runtime fica container-aware: GOMAXPROCS respeita o cgroup CPU limit sem gambiarra. Em versões anteriores, um container com 0,5 CPU em host de 16 cores usava 16 threads de runtime. Veja o guia de GOMAXPROCS container-aware no blog.

Como corrigir go build: no such file or directory com -o /out?

Identifique a etapa e leia o caminho citado no erro. Para um único pacote main, -o /out pode gerar um arquivo chamado /out; -o /out/server define explicitamente o executável que o COPY deve buscar. Go 1.27.1 cria diretórios pais ausentes: mkdir não é uma correção geral. Confira WORKDIR, contexto, COPY e pacote de entrada no build; se o binário existe mas falha ao iniciar, verifique o loader e as bibliotecas da imagem final.

O que significam Docker Go e Docker Golang?

São a mesma busca: containerizar e fazer deploy de uma aplicação escrita em Go (Golang) com Docker. Na prática isso significa Dockerfile multi-stage, binário estático com CGO_ENABLED=0, imagem final scratch ou distroless e, em desenvolvimento, Compose com banco. Go e Golang são a mesma linguagem; as duas formas aparecem juntas nas buscas.