Zig e Docker em 2026: Containers Pequenos, Seguros e Reproduzíveis

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árioOptimize recomendado
API, worker, serviço internoReleaseSafe
CLI distribuída em containerReleaseSmall ou ReleaseSafe
Benchmark controlado, hot path medidoReleaseFast
Debug em ambiente de desenvolvimentoDebug

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:

StackImagem final típica
Zig + scratch3–10 MB
Go + scratch8–25 MB
Rust + scratch/distroless5–30 MB
Node.js + Alpine80–200 MB
Python slim80–180 MB
Java JRE150–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, ReleaseSmall ou ReleaseFast de forma consciente.
  • A imagem final não contém código-fonte, cache, .git, .env ou 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:

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

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.