---
title: "Zig no GitHub Actions: Release Multiplataforma com Binários Pequenos"
url: "https://ziglang.com.br/artigos/zig-github-actions-release-multiplataforma/"
markdown_url: "https://ziglang.com.br/artigos/zig-github-actions-release-multiplataforma.MD"
description: "Como configurar CI/CD para projetos Zig no GitHub Actions: cache, testes, build matrix, targets Linux/macOS/Windows, artefatos e releases reproduzíveis."
date: "2026-05-17"
author: ""
---

# Zig no GitHub Actions: Release Multiplataforma com Binários Pequenos

Como configurar CI/CD para projetos Zig no GitHub Actions: cache, testes, build matrix, targets Linux/macOS/Windows, artefatos e releases reproduzíveis.


Um dos motivos para usar Zig em ferramentas de infraestrutura é simples: o mesmo projeto consegue gerar binários nativos para Linux, macOS, Windows, BSD e WebAssembly sem montar uma coleção frágil de toolchains externos. Essa vantagem fica ainda mais forte quando o release é automatizado no GitHub Actions.

Em vez de compilar manualmente na sua máquina e enviar um `.zip` por intuição, um pipeline bem desenhado roda testes, valida formatação, compila uma matriz de targets, empacota artefatos com nomes previsíveis e publica tudo em uma release. Para CLIs, servidores pequenos, agentes, parsers, ferramentas de build e utilitários internos, esse é o caminho entre "funciona aqui" e "qualquer pessoa baixa o binário certo".

Este guia mostra um fluxo prático para projetos Zig. Ele complementa o tutorial de [cross-compilation em Zig](/artigos/zig-cross-compilation-guia/), o guia de [containers Docker otimizados](/artigos/zig-docker-containers/) e o material de [debugging e profiling](/artigos/zig-depuracao-profiling-tracy-valgrind-perf/). A diferença aqui é o foco operacional: como transformar o build em uma rotina repetível de CI/CD.

## O que um bom pipeline Zig precisa fazer

Um pipeline mínimo que só executa `zig build` já ajuda, mas ainda deixa várias lacunas. Para release de verdade, pense em cinco etapas:

1. **Preparar versão fixa do Zig.** O runner precisa usar a mesma versão que você usa localmente.
2. **Rodar verificações rápidas.** `zig fmt --check`, `zig build test` e, quando existir, testes de integração.
3. **Compilar em matriz de targets.** Linux x86_64, Linux ARM64, macOS ARM64, macOS x86_64 e Windows x86_64 costumam cobrir a maior parte dos usuários.
4. **Empacotar artefatos.** Nomeie arquivos com projeto, versão, sistema e arquitetura.
5. **Publicar release só em tag.** Pull requests validam; tags publicam.

Essa separação evita um erro comum: tratar CI e release como a mesma coisa. Todo commit deve testar. Nem todo commit deve gerar um pacote público.

## Estrutura esperada do projeto

Vamos assumir um projeto padrão:

```text
meu-cli/
  build.zig
  build.zig.zon
  src/
    main.zig
```

O `build.zig` deve expor as opções padrão de target e optimize. Se você usou `zig init`, provavelmente já tem algo parecido:

```zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "meu-cli",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    b.installArtifact(exe);

    const run_tests = b.addTest(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    const test_step = b.step("test", "Roda testes");
    test_step.dependOn(&b.addRunArtifact(run_tests).step);
}
```

O ponto importante é que o pipeline consiga chamar `zig build -Dtarget=... -Doptimize=ReleaseSafe` sem editar código. Isso é o que torna a matriz simples.

## Workflow de CI para pull requests

Crie `.github/workflows/ci.yml`:

```yaml
name: CI

on:
  pull_request:
  push:
    branches: [main, master]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: mlugg/setup-zig@v2
        with:
          version: 0.14.1

      - name: Check formatting
        run: zig fmt --check src build.zig

      - name: Run tests
        run: zig build test

      - name: Build debug
        run: zig build
```

O `zig fmt --check` merece ficar no início. Formatação quebrada é barata de corrigir e não vale gastar minutos de build antes de descobrir. Se seu projeto tem exemplos, adicione `examples` ao comando. Se tem mais diretórios Zig fora de `src`, inclua também.

Sobre a versão: fixe uma versão explícita. Usar `master` ou `latest` parece conveniente, mas transforma o pipeline em loteria. Zig ainda está antes da versão 1.0; mudanças na linguagem e na standard library são normais. Para projetos com vários contribuidores, registre a versão também no README e considere usar [zigup](/glossario/zigup/) localmente para alinhar ambiente.

## Release com matriz de targets

Agora crie `.github/workflows/release.yml`:

```yaml
name: Release

on:
  push:
    tags:
      - "v*"

permissions:
  contents: write

jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-linux-musl
            os: ubuntu-latest
            artifact: meu-cli-linux-x86_64
          - target: aarch64-linux-musl
            os: ubuntu-latest
            artifact: meu-cli-linux-aarch64
          - target: x86_64-macos
            os: macos-latest
            artifact: meu-cli-macos-x86_64
          - target: aarch64-macos
            os: macos-latest
            artifact: meu-cli-macos-aarch64
          - target: x86_64-windows-gnu
            os: ubuntu-latest
            artifact: meu-cli-windows-x86_64.exe

    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4

      - uses: mlugg/setup-zig@v2
        with:
          version: 0.14.1

      - name: Build release
        run: zig build -Dtarget=${{ matrix.target }} -Doptimize=ReleaseSafe

      - name: Prepare artifact
        shell: bash
        run: |
          mkdir -p dist
          if [[ "${{ matrix.artifact }}" == *.exe ]]; then
            cp zig-out/bin/meu-cli.exe "dist/${{ matrix.artifact }}"
          else
            cp zig-out/bin/meu-cli "dist/${{ matrix.artifact }}"
            chmod +x "dist/${{ matrix.artifact }}"
          fi

      - uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.artifact }}
          path: dist/${{ matrix.artifact }}
```

Esse job ainda não publica a release. Ele apenas compila e guarda artefatos. A etapa de publicação fica separada para não misturar permissões de escrita com cada build da matriz.

```yaml
  publish:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          path: dist
          merge-multiple: true

      - name: Checksums
        run: |
          cd dist
          sha256sum * > SHA256SUMS.txt

      - uses: softprops/action-gh-release@v2
        with:
          files: |
            dist/*
```

O arquivo `SHA256SUMS.txt` é pequeno, mas transmite seriedade. Quem usa sua ferramenta em automação pode verificar integridade antes de executar o binário baixado.

## ReleaseSafe, ReleaseFast ou ReleaseSmall?

Para a maioria dos projetos publicados, comece com `ReleaseSafe`. Ele otimiza bastante, mas mantém verificações de segurança importantes. `ReleaseFast` remove checks para extrair performance máxima; use quando você mediu, entendeu o risco e tem testes bons. `ReleaseSmall` é excelente para utilitários embarcados, WebAssembly e imagens mínimas, mas pode sacrificar velocidade.

Uma política simples:

| Cenário | Modo recomendado |
|---|---|
| CLI pública | `ReleaseSafe` |
| API interna crítica | `ReleaseSafe` |
| Parser medido em benchmark | `ReleaseFast`, se os testes cobrirem bem |
| Ferramenta embarcada | `ReleaseSmall` |
| Debug de incidente | `Debug` ou `ReleaseSafe` com símbolos |

Se a sua ferramenta processa entrada não confiável, prefira segurança. Um binário 5% mais rápido não compensa comportamento indefinido em dados vindos de usuários.

## Cache: útil, mas não mágico

Zig usa cache local para acelerar builds. No GitHub Actions, você pode guardar `.zig-cache` e o cache global, mas faça isso com cuidado. Cache errado gera bugs difíceis de entender.

```yaml
      - uses: actions/cache@v4
        with:
          path: |
            .zig-cache
            ~/.cache/zig
          key: zig-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('build.zig', 'build.zig.zon') }}
          restore-keys: |
            zig-${{ runner.os }}-${{ matrix.target }}-
```

Inclua `matrix.target` na chave. Um cache de Linux musl não deve ser reutilizado como se fosse Windows. Inclua `build.zig.zon` porque dependências mudam o resultado. Para projetos pequenos, cache pode nem valer a complexidade; para projetos com dependências C, LLVM pesado ou geração de código, costuma pagar rapidamente.

## Versões, tags e changelog

Use tags semânticas como `v0.3.0`. O fluxo fica previsível:

```bash
git tag v0.3.0
git push origin v0.3.0
```

Antes da tag, rode localmente:

```bash
zig fmt --check src build.zig
zig build test
zig build -Doptimize=ReleaseSafe
```

Se o projeto usa dependências no `build.zig.zon`, não edite hashes manualmente sem entender o que está fazendo. Use `zig fetch --save` para registrar a dependência e deixar o manifesto refletir a origem real. Isso conversa diretamente com a filosofia de reprodutibilidade do Zig.

## Containers e binários: quando usar cada um

Para ferramentas de linha de comando, publicar binários diretos costuma ser melhor que publicar apenas uma imagem Docker. O usuário baixa, coloca no `PATH` e acabou. Para serviços HTTP, agentes de infraestrutura e jobs de Kubernetes, imagem Docker também faz sentido.

A boa notícia é que os dois caminhos podem coexistir. O mesmo release gera binários e um job separado monta a imagem. Se o serviço usa HTTP, leia também o guia de [Zig HTTP Server em produção](/artigos/zig-http-server-producao/), porque CI/CD não substitui limites de body, health check, logs e shutdown.

Para comparar com outras linguagens do portfólio, projetos Go têm um caminho parecido de release multiplataforma, mas normalmente dependem de `GOOS` e `GOARCH`. Veja o material de <a href="https://golang.com.br/artigos/go-benchmark-testing/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">benchmarks e testes em Go</a> para entender a diferença cultural: Go privilegia convenção; Zig privilegia controle explícito de target, modo de otimização e dependências.

## Checklist final

Antes de publicar seu próximo release Zig, confirme:

- A versão do Zig está fixa no workflow.
- `zig fmt --check` roda em todo pull request.
- `zig build test` roda antes de qualquer release.
- A matriz cobre os sistemas que seus usuários realmente usam.
- Os artefatos têm nomes claros, com sistema e arquitetura.
- A release inclui checksums SHA-256.
- `ReleaseSafe` é o padrão, salvo motivo medido para trocar.
- Cache inclui target e arquivos de build na chave.
- A tag é a única coisa que publica artefatos públicos.

Zig brilha quando o build é tratado como parte do produto. A linguagem já entrega cross-compilation, binários pequenos e integração forte com C; o pipeline só precisa não desperdiçar essas vantagens. Com GitHub Actions bem configurado, um projeto Zig deixa de ser "código que compila na máquina do mantenedor" e vira uma ferramenta distribuível, verificável e pronta para uso real.
