---
title: "Zig em Produção: Configuração Segura, Env Vars e Segredos"
url: "https://ziglang.com.br/artigos/zig-configuracao-segura-segredos-env/"
markdown_url: "https://ziglang.com.br/artigos/zig-configuracao-segura-segredos-env.MD"
description: "Como desenhar configuração segura em aplicações Zig: variáveis de ambiente, arquivos locais, validação no boot, segredos, logs, testes e deploy sem hardcode."
date: "2026-05-22"
author: ""
---

# Zig em Produção: Configuração Segura, Env Vars e Segredos

Como desenhar configuração segura em aplicações Zig: variáveis de ambiente, arquivos locais, validação no boot, segredos, logs, testes e deploy sem hardcode.


Uma aplicação Zig pequena costuma começar simples: um binário, algumas flags, talvez uma URL de API e um token para falar com um serviço externo. O problema aparece quando esse binário sai do notebook do desenvolvedor e entra em produção. Onde ficam as variáveis de ambiente? Quem valida configuração obrigatória? Como evitar token hardcoded? O que pode ir para log? Como testar erro de configuração sem depender do ambiente real?

Essas perguntas parecem operacionais, mas afetam diretamente a qualidade do código. Zig ajuda porque incentiva decisões explícitas: nada de runtime mágico carregando `.env`, nada de framework escondendo defaults perigosos, nada de configuração global mutável sem você perceber. Essa mesma explicitude, porém, exige disciplina. Se o projeto não define uma fronteira clara para configuração, cada módulo começa a chamar `std.process.getEnvVarOwned`, interpretar strings do seu jeito e vazar detalhes em mensagens de erro.

Este guia mostra uma abordagem prática para **configuração segura em Zig**: separar configuração pública de segredo, validar tudo no boot, passar uma struct tipada para o restante da aplicação, evitar hardcode, manter logs úteis sem expor credenciais e integrar isso com Docker, CI, CLIs e serviços HTTP. Ele complementa os artigos de [servidor HTTP em produção](/artigos/zig-http-server-producao/), [containers Docker pequenos e seguros](/artigos/zig-docker-containers/), [criar CLI profissional em Zig](/artigos/zig-cli-aplicacao-linha-comando/) e [Zig com SQLite para ferramentas locais](/artigos/zig-sqlite-ferramentas-locais/).

## O que é configuração e o que é segredo

Antes de escrever código, separe os tipos de dado. Nem tudo que vem do ambiente é segredo.

Configuração pública inclui porta HTTP, nível de log, URL base de um serviço interno, caminho de cache, modo de execução, tamanho máximo de payload e feature flags não sensíveis. Ela pode aparecer em log de boot, documentação e exemplos.

Segredo é qualquer dado que permite acesso, assinatura, impersonação ou descriptografia: token de API, senha de banco, chave privada, cookie de sessão, chave de webhook, DSN com credencial embutida ou seed criptográfica. Segredo não deve ir para Git, imagem Docker, argumento de build, mensagem de erro, log estruturado, métrica, dump de panic ou página de status.

A regra prática é simples: se alguém colar esse valor em outro lugar e conseguir agir como sua aplicação, trate como segredo.

Em Zig, vale refletir essa diferença no tipo:

```zig
const Config = struct {
    port: u16,
    log_level: LogLevel,
    database_path: []const u8,
    api_base_url: []const u8,
    api_token: Secret,
};

const Secret = struct {
    value: []const u8,

    pub fn redacted(_: Secret) []const u8 {
        return "[redigido]";
    }
};
```

O wrapper `Secret` não é criptografia. Ele é um freio de engenharia: obriga o código a pensar duas vezes antes de imprimir o valor.

## Carregue configuração uma vez no boot

Evite espalhar leitura de ambiente pela aplicação. Um serviço saudável carrega configuração no início, valida, monta uma struct imutável e passa essa struct para os componentes que precisam dela. Isso reduz surpresa e facilita teste.

Um esqueleto fica assim:

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

const ConfigError = error{
    MissingApiToken,
    InvalidPort,
    InvalidLogLevel,
};

pub fn loadConfig(allocator: std.mem.Allocator) !Config {
    const port_raw = std.process.getEnvVarOwned(allocator, "PORT") catch |err| switch (err) {
        error.EnvironmentVariableNotFound => try allocator.dupe(u8, "8080"),
        else => return err,
    };
    defer allocator.free(port_raw);

    const port_num = std.fmt.parseInt(u16, port_raw, 10) catch return ConfigError.InvalidPort;

    const token = std.process.getEnvVarOwned(allocator, "API_TOKEN") catch |err| switch (err) {
        error.EnvironmentVariableNotFound => return ConfigError.MissingApiToken,
        else => return err,
    };

    return Config{
        .port = port_num,
        .log_level = .info,
        .database_path = try allocator.dupe(u8, "./data/app.sqlite"),
        .api_base_url = try allocator.dupe(u8, "https://api.exemplo.com"),
        .api_token = Secret{ .value = token },
    };
}
```

Em um projeto real, crie também `deinit` para liberar strings duplicadas. A ideia importante é a fronteira: `loadConfig` é o único lugar que conversa com o ambiente. O resto do código recebe `Config`.

## Falhe cedo e com mensagem clara

Configuração inválida deve derrubar o processo antes de aceitar tráfego, processar fila ou escrever no banco. Isso parece duro, mas é mais seguro do que rodar meia hora com token vazio, URL errada ou porta inesperada.

Ao iniciar, valide pelo menos:

- presença de segredos obrigatórios;
- formato de números, URLs e paths;
- limites mínimos e máximos;
- combinação de flags incompatíveis;
- diretórios graváveis quando a aplicação precisa persistir dados;
- modo de produção sem defaults inseguros.

Mensagens de erro precisam dizer o que falta, não o valor recebido. Escreva `API_TOKEN ausente` em vez de imprimir o token. Para valores públicos, como `PORT`, a mensagem pode mostrar o valor problemático se isso ajudar o operador.

```zig
pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();
    var config = loadConfig(allocator) catch |err| {
        std.log.err("configuração inválida: {}", .{err});
        return err;
    };
    defer config.deinit(allocator);

    std.log.info("iniciando serviço na porta {d}", .{config.port});
    try runServer(config);
}
```

Esse padrão também facilita health check. Um processo que subiu já passou por validação mínima. Se a configuração estiver quebrada, o orquestrador vê falha de boot em vez de um serviço aparentemente saudável.

## Não carregue `.env` automaticamente em produção

Arquivos `.env` são úteis no desenvolvimento local, mas perigosos quando viram dependência implícita. Em produção, prefira variáveis injetadas pelo orquestrador ou um secret manager: Kubernetes Secrets, Docker secrets, SOPS, Infisical, Vault, AWS Secrets Manager, GCP Secret Manager ou a ferramenta que sua equipe já opera.

Se o projeto aceita `.env`, trate como conveniência local e documente isso claramente. Não faça o binário procurar `.env` em diretórios arbitrários em produção. Essa busca automática pode carregar o arquivo errado, dificultar auditoria e criar diferença entre CI, staging e produção.

Para uma CLI Zig, o equilíbrio costuma ser:

- flags para opções temporárias;
- arquivo de configuração em `~/.config/sua-ferramenta/config.toml` para preferências não sensíveis;
- variáveis de ambiente para segredos ocasionais;
- keychain, secret manager ou integração do sistema para credenciais persistentes.

Para um serviço, mantenha a configuração declarada no deploy. O binário deve ser previsível: recebe ambiente, valida e executa.

## Segredos não pertencem ao build

Um erro comum é passar token como argumento de build, gravar segredo em `build.zig.zon`, embutir uma chave com `@embedFile` ou gerar um arquivo Zig com constantes sensíveis. Isso transforma credencial em artefato. O segredo pode acabar em cache de CI, camada de imagem, binário distribuído, log de build ou histórico de shell.

Use o build para coisas que realmente são propriedades do artefato: versão, commit, target, modo de otimização, feature compile-time pública. Use runtime config para dados que mudam por ambiente ou exigem rotação.

Essa separação combina com o guia de [release multiplataforma com GitHub Actions](/artigos/zig-github-actions-release-multiplataforma/): o CI deve produzir artefatos reproduzíveis, não binários diferentes para cada segredo de ambiente.

## Logs úteis sem vazamento

Logs de configuração são bons. Eles ajudam a investigar deploy quebrado. Mas precisam ser seletivos.

Pode ir para log:

- porta;
- modo de execução;
- caminho de banco local, se não contiver usuário sensível;
- host de API sem token;
- tamanho máximo de requisição;
- flags públicas.

Não deve ir para log:

- token inteiro ou parcial sem necessidade;
- URL com usuário e senha;
- header `Authorization`;
- payload de webhook com segredo;
- conteúdo de arquivo de credencial;
- dump completo da struct `Config` se ela contém `Secret`.

Em Zig, evite implementar `format` para `Secret` retornando o valor real. Se você precisa identificar qual credencial foi usada, prefira imprimir o nome lógico, como `API_TOKEN`, ou um fingerprint curto calculado de forma intencional e documentada. Mesmo fingerprint deve ser usado com moderação.

## Teste configuração como código de produto

Configuração quebrada derruba produção. Então ela merece teste.

Uma técnica simples é separar parsing puro de leitura do ambiente. Em vez de testar `std.process` diretamente, crie uma função que recebe um mapa de strings ou uma pequena interface de lookup. Assim você valida casos sem depender do ambiente do processo de teste.

```zig
const Env = std.StringHashMap([]const u8);

fn parseConfig(allocator: std.mem.Allocator, env: *const Env) !Config {
    const port_raw = env.get("PORT") orelse "8080";
    const token_raw = env.get("API_TOKEN") orelse return ConfigError.MissingApiToken;

    return Config{
        .port = std.fmt.parseInt(u16, port_raw, 10) catch return ConfigError.InvalidPort,
        .log_level = .info,
        .database_path = try allocator.dupe(u8, "./data/app.sqlite"),
        .api_base_url = try allocator.dupe(u8, "https://api.exemplo.com"),
        .api_token = Secret{ .value = try allocator.dupe(u8, token_raw) },
    };
}
```

Com isso, seus testes cobrem token ausente, porta inválida, default de porta, modo de produção e limites. Para CLIs, teste também precedência: flag vence variável de ambiente? Arquivo local vence default? Documente e congele essa ordem.

## Configuração para serviços HTTP

Em servidores HTTP Zig, configuração segura aparece em pontos específicos:

- `PORT` define onde o processo escuta;
- `BODY_LIMIT_BYTES` protege memória e parsing;
- `READ_TIMEOUT_MS` e `WRITE_TIMEOUT_MS` evitam conexões presas;
- `DATABASE_URL` ou `DATABASE_PATH` aponta persistência;
- `TRUST_PROXY` só deve ser true quando há proxy controlado;
- `WEBHOOK_SECRET` valida chamadas externas;
- `LOG_LEVEL` ajusta diagnóstico sem mudar binário.

Não coloque TLS diretamente no binário só por orgulho técnico. Como discutido no guia de [HTTP server em produção](/artigos/zig-http-server-producao/), muitas aplicações Zig ficam melhores atrás de Caddy, Nginx, Cloudflare, Traefik ou load balancer. A configuração do Zig deve refletir essa fronteira: o processo confia em headers de proxy apenas quando o deploy garante que ninguém acessa a porta interna diretamente.

## Configuração para CLIs e agentes locais

CLIs têm outro problema: experiência de usuário. Se toda execução exige dez variáveis de ambiente, ninguém usa. Se a CLI grava token em texto puro sem avisar, vira risco.

Uma CLI Zig madura pode seguir esta ordem:

1. flag explícita para a execução atual;
2. variável de ambiente para automação e CI;
3. arquivo de configuração para preferências não sensíveis;
4. armazenamento seguro do sistema para credenciais persistentes;
5. default seguro quando existir.

Para ferramentas locais com banco embutido, como no artigo de [Zig e SQLite](/artigos/zig-sqlite-ferramentas-locais/), salve no banco apenas o que precisa persistir. Token de API raramente precisa morar no SQLite da ferramenta. Quando precisar, criptografe com mecanismo do sistema ou delegue ao keychain em vez de inventar criptografia caseira.

## Integração com Docker e CI

No Docker, nunca grave segredo no Dockerfile. Também evite `ARG API_TOKEN`, porque argumentos de build podem aparecer no histórico da imagem. Prefira passar segredo em runtime pelo orquestrador.

Um `docker-compose.yml` local pode usar variável de ambiente, mas o exemplo de documentação deve deixar claro que produção usa secret manager:

```yaml
services:
  app:
    image: exemplo/zig-app:latest
    environment:
      PORT: "8080"
      LOG_LEVEL: "info"
      API_BASE_URL: "https://api.exemplo.com"
    secrets:
      - api_token

secrets:
  api_token:
    file: ./secrets/api_token.txt
```

No lado Zig, se o ambiente expõe segredo como arquivo, leia o arquivo com limite de tamanho, remova quebra de linha final e aplique a mesma validação. Não aceite arquivo de segredo com megabytes. Segredo grande demais normalmente é erro de deploy.

## Checklist de produção

Antes de publicar um serviço ou CLI Zig que depende de configuração, revise:

- a aplicação carrega configuração em um único módulo;
- segredos usam tipo próprio ou política clara de redaction;
- boot falha se configuração obrigatória está ausente;
- mensagens de erro não imprimem credenciais;
- logs mostram apenas configuração pública;
- CI não injeta segredo no build final;
- Dockerfile não contém token, senha ou `.env` copiado;
- documentação diferencia ambiente local e produção;
- testes cobrem configuração ausente e inválida;
- rotação de segredo não exige recompilar o binário;
- health check só responde depois da validação mínima.

## Onde Zig ajuda de verdade

Zig não resolve segredo automaticamente. Nenhuma linguagem resolve. O que Zig oferece é uma base honesta: inicialização explícita, tipos simples, controle de memória, binário previsível e pouca magia em runtime. Isso é ótimo para configuração segura, desde que o projeto use essa clareza a favor da operação.

O objetivo não é criar um framework de configuração complexo. É o contrário: uma struct `Config`, um módulo de carregamento, validação cedo, segredo tratado como segredo e logs sem vazamento. Para muitos serviços, CLIs, agentes locais e ferramentas internas, esse desenho já elimina a maior parte dos acidentes comuns.

Se o próximo passo do projeto é expor uma API, leia também o guia de [servidor HTTP em produção](/artigos/zig-http-server-producao/). Se a preocupação é empacotar e distribuir, combine este checklist com [Docker em Zig](/artigos/zig-docker-containers/) e [release multiplataforma no GitHub Actions](/artigos/zig-github-actions-release-multiplataforma/). E, se você vem de ecossistemas com configuração mais dinâmica, vale comparar com o material irmão de <a href="https://golang.com.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go Lang Brasil</a>, onde a simplicidade operacional também costuma pesar bastante em serviços de produção.
