---
title: "Zig e Docker em 2026: Containers Pequenos, Seguros e Reproduzíveis"
url: "https://ziglang.com.br/artigos/zig-docker-containers/"
markdown_url: "https://ziglang.com.br/artigos/zig-docker-containers.MD"
description: "Guia prático de Docker para Zig: multi-stage build, imagens scratch, musl, usuário não-root, health check, build multi-arch, cache e deploy em produção."
date: "2026-02-21"
author: ""
---

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

Guia prático de Docker para Zig: multi-stage build, imagens scratch, musl, usuário não-root, health check, build multi-arch, cache e deploy em produção.


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](/artigos/zig-github-actions-release-multiplataforma/) e o material de [cross-compilation em Zig](/artigos/zig-cross-compilation-guia/). 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.

```dockerfile
# 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:

```dockerfile
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
```

Rode com UID numérico, não com `root`:

```dockerfile
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:

```dockerfile
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:

```bash
/app --health
```

No Dockerfile:

```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](/artigos/zig-http-server-producao/).

## 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:

```yaml
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`.

```bash
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:

```dockerfile
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`, `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 <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go no Brasil</a> e de <a href="https://rustlang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">Rust no Brasil</a> 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](/artigos/zig-github-actions-release-multiplataforma/) — CI/CD, artefatos e checksums.
- [Zig HTTP Server em Produção](/artigos/zig-http-server-producao/) — proxy, logs, limites e health check.
- [Cross-compilation em Zig](/artigos/zig-cross-compilation-guia/) — targets e binários para vários sistemas.
- [Debugging e profiling em Zig](/artigos/zig-depuracao-profiling-tracy-valgrind-perf/) — quando o container está pequeno, mas o comportamento ainda precisa ser medido.
- [Zig Cloud Native](/artigos/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](/artigos/zig-kubernetes-operators/) — Deploy de operators em imagens pequenas
