Zig combina muito bem com Docker porque entrega uma coisa que containers valorizam: binários nativos, pequenos e previsíveis. Em vez de carregar runtime, gerenciador de pacotes, interpretador e metade de uma distribuição Linux para rodar uma API simples, um projeto Zig pode compilar para x86_64-linux-musl ou aarch64-linux-musl e entrar em uma imagem scratch com poucos megabytes.
Isso não significa que todo Dockerfile Zig deva ser minimalista a qualquer custo. Em produção, tamanho é só uma parte da equação. O container também precisa ser reproduzível, não rodar como root, ter certificados quando acessa HTTPS, expor logs corretamente, aceitar variáveis de ambiente e ser simples de reconstruir no CI. Este guia atualiza a abordagem de Docker para Zig em 2026 com esse equilíbrio: imagem pequena, mas operável.
Se você ainda está montando o fluxo de build, leia também o guia de GitHub Actions para releases multiplataforma e o material de cross-compilation em Zig. Docker entra depois: ele empacota o binário que você já consegue compilar de forma repetível.
O padrão recomendado: multi-stage build
A forma mais limpa é separar o ambiente de compilação da imagem final. O estágio builder tem Zig, dependências e cache. O estágio final tem só o binário e os arquivos mínimos para execução.
# syntax=docker/dockerfile:1.7
FROM alpine:3.20 AS builder
ARG ZIG_VERSION=0.14.1
ARG TARGET=x86_64-linux-musl
ARG OPTIMIZE=ReleaseSafe
RUN apk add --no-cache curl tar xz ca-certificates
RUN curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz" \
| tar -xJ -C /opt && \
ln -s "/opt/zig-linux-x86_64-${ZIG_VERSION}/zig" /usr/local/bin/zig
WORKDIR /src
COPY build.zig build.zig.zon ./
COPY src ./src
RUN zig build -Dtarget=${TARGET} -Doptimize=${OPTIMIZE}
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /src/zig-out/bin/app /app
USER 10001:10001
EXPOSE 8080
ENTRYPOINT ["/app"]
Esse Dockerfile evita dois erros comuns. Primeiro, ele não instala Zig na imagem final. Segundo, ele fixa a versão do compilador. Usar latest em CI parece confortável, mas Zig ainda muda bastante antes da versão 1.0; builds reproduzíveis pedem versão explícita.
Por que ReleaseSafe costuma ser o melhor padrão
Para containers de produção, ReleaseSafe é um bom ponto de partida. Ele entrega otimização forte sem remover todas as verificações de segurança. ReleaseFast pode ser melhor em um serviço de altíssimo throughput, mas só depois de benchmark e teste. ReleaseSmall reduz tamanho, útil para sidecars, CLIs e ambientes embarcados, mas nem sempre é a melhor escolha para latência.
Uma política simples:
| Cenário | Optimize recomendado |
|---|---|
| API, worker, serviço interno | ReleaseSafe |
| CLI distribuída em container | ReleaseSmall ou ReleaseSafe |
| Benchmark controlado, hot path medido | ReleaseFast |
| Debug em ambiente de desenvolvimento | Debug |
O ganho real de Zig no Docker não depende de espremer cada byte. A grande diferença já vem de não depender de runtime pesado.
scratch é ótimo, mas tem pegadinhas
A imagem scratch não tem shell, ls, cat, timezone database, usuário cadastrado, DNS tools nem certificados por padrão. Isso é excelente para superfície de ataque, mas muda a forma de operar.
Inclua certificados se o binário faz chamadas HTTPS:
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
Rode com UID numérico, não com root:
USER 10001:10001
E escreva logs em stdout/stderr. Dentro de scratch, não conte com /var/log/app.log nem com ferramentas interativas para depurar. O container deve ser observável de fora.
Se seu serviço precisa de arquivos de timezone, templates, migrações ou assets estáticos, copie cada diretório explicitamente:
COPY --from=builder /src/assets /assets
Não use COPY . . na etapa final. Isso joga código-fonte, arquivos de teste e segredos acidentais para dentro da imagem.
Health check sem inflar a imagem
Em imagens baseadas em Debian ou Alpine, muita gente usa curl no HEALTHCHECK. Em scratch, não existe curl. A solução mais limpa é o próprio binário expor um modo de verificação.
Exemplo de contrato:
/app --health
No Dockerfile:
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD ["/app", "--health"]
No código, --health deve validar o mínimo: parse de configuração, porta preparada quando aplicável, conexão obrigatória com dependências críticas e retorno 0 se está saudável. Para APIs HTTP, também vale expor /healthz, como explicado no guia de Zig HTTP Server em produção.
Docker Compose para desenvolvimento
Para desenvolvimento local, a meta não precisa ser scratch. Um compose com bind mount e rebuild simples acelera iteração:
services:
api:
build:
context: .
dockerfile: Dockerfile
args:
ZIG_VERSION: 0.14.1
TARGET: x86_64-linux-musl
OPTIMIZE: Debug
ports:
- "8080:8080"
environment:
DATABASE_URL: postgres://app:app@db:5432/app
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Em produção, prefira passar segredos pelo orquestrador: Docker secrets, Kubernetes Secrets, SOPS, Infisical, Vault ou a ferramenta que seu ambiente já usa. Nunca grave tokens no Dockerfile, no docker-compose.yml ou em argumentos de build; argumentos aparecem em histórico de imagem e logs de CI com facilidade.
Build multi-arch com Zig e Docker Buildx
Zig facilita cross-compilation, mas Docker ainda precisa publicar manifests para múltiplas arquiteturas. Um fluxo comum é compilar uma imagem por target e usar buildx para empurrar linux/amd64 e linux/arm64.
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg ZIG_VERSION=0.14.1 \
--build-arg OPTIMIZE=ReleaseSafe \
-t ghcr.io/sua-org/sua-api:1.2.0 \
-t ghcr.io/sua-org/sua-api:latest \
--push .
Se o seu build.zig usa b.standardTargetOptions, aceite TARGET como argumento do Dockerfile e traduza a plataforma para o target Zig. Em pipelines maiores, eu prefiro gerar binários em uma matriz de CI e deixar o build da imagem só copiar o artefato certo. Isso reduz surpresas e deixa o mesmo binário disponível fora do Docker.
Cache: acelere sem sacrificar reprodutibilidade
Em projetos Zig simples, o build já é rápido. Em projetos com dependências, cache ajuda. Com Dockerfile moderno, use mounts de cache:
RUN --mount=type=cache,target=/root/.cache/zig \
--mount=type=cache,target=/root/.cache/zig/p \
zig build -Dtarget=${TARGET} -Doptimize=${OPTIMIZE}
O cache não deve ser necessário para o build funcionar. Ele só acelera. Se apagar o cache muda o resultado, o problema está no projeto, não no Docker.
Comparação realista de tamanho
Os números variam conforme dependências, símbolos, modo de otimização e se você inclui certificados ou assets. Como ordem de grandeza:
| Stack | Imagem final típica |
|---|---|
Zig + scratch | 3–10 MB |
Go + scratch | 8–25 MB |
Rust + scratch/distroless | 5–30 MB |
| Node.js + Alpine | 80–200 MB |
| Python slim | 80–180 MB |
| Java JRE | 150–350 MB |
A comparação justa não é “Zig sempre ganha de tudo”. A vantagem é que Zig deixa o caminho pequeno ser o caminho natural. Para ferramentas de infraestrutura, APIs leves, proxies, parsers, agentes locais e sidecars, isso vira deploy mais rápido, menos tráfego de registry e menor superfície de ataque.
Checklist de produção
Antes de publicar a imagem, verifique:
- A versão do Zig está fixa.
- O build usa
ReleaseSafe,ReleaseSmallouReleaseFastde forma consciente. - A imagem final não contém código-fonte, cache,
.git,.envou ferramentas de build. - O processo roda com UID não-root.
- Certificados TLS foram copiados se o serviço acessa HTTPS.
- Logs vão para stdout/stderr.
- Existe health check ou endpoint
/healthz. - Tags são imutáveis para release (
1.2.0) e opcionais para conveniência (latest). - O CI gera SBOM, checksum ou assinatura quando o ambiente exige rastreabilidade.
Quando não usar scratch
Use Alpine, Debian slim ou distroless quando você precisa de shell para diagnóstico, bibliotecas dinâmicas, timezone database completa, ferramentas auxiliares ou compatibilidade operacional com uma plataforma específica. scratch é ótimo para binários autocontidos; não é medalha de honra obrigatória.
Para times que já operam Go em containers, o salto mental é pequeno: binário estático, imagem mínima, logs no stdout e configuração por ambiente. A diferença é que Zig também funciona como ferramenta de cross-compilation e integração com C. Se você está comparando opções para infraestrutura, vale ler o material de Go no Brasil e de Rust no Brasil para entender onde cada ecossistema brilha.
Próximos passos
Depois que a imagem estiver limpa, o próximo gargalo costuma ser entrega. Combine este guia com:
- Zig no GitHub Actions: release multiplataforma — CI/CD, artefatos e checksums.
- Zig HTTP Server em Produção — proxy, logs, limites e health check.
- Cross-compilation em Zig — targets e binários para vários sistemas.
- Debugging e profiling em Zig — quando o container está pequeno, mas o comportamento ainda precisa ser medido.
- Zig Cloud Native — visão mais ampla de deploy em nuvem.
Docker com Zig não é só uma receita para imagem pequena. É uma disciplina: compilador fixo, artefato previsível, container mínimo, operação explícita e release rastreável. Quando esse conjunto fecha, Zig vira uma opção muito forte para serviços pequenos, ferramentas internas e infraestrutura que precisa ser rápida de distribuir e simples de rodar.
Conteúdo Relacionado
- Kubernetes Operators em Zig — Deploy de operators em imagens pequenas