Deploy em Produção de Aplicações Zig: Guia Completo

Este é o artigo final da série Desenvolvimento Web com Zig. Depois de construir o servidor, roteamento, API REST e middlewares, vamos preparar tudo para produção.

Configuração via Variáveis de Ambiente

Aplicações em produção não devem ter configurações hard-coded:

const std = @import("std");

const Config = struct {
    host: []const u8,
    porta: u16,
    modo: enum { desenvolvimento, producao },
    log_level: enum { debug, info, warn, erro },
    max_conexoes: u32,

    fn fromEnv() Config {
        const env = std.process.getenv;

        const porta_str = env("PORT") orelse "8080";
        const porta = std.fmt.parseInt(u16, porta_str, 10) catch 8080;

        const modo_str = env("APP_MODE") orelse "desenvolvimento";
        const modo: @TypeOf((Config{}).modo) = if (std.mem.eql(u8, modo_str, "producao"))
            .producao
        else
            .desenvolvimento;

        return .{
            .host = env("HOST") orelse "0.0.0.0",
            .porta = porta,
            .modo = modo,
            .log_level = if (modo == .producao) .info else .debug,
            .max_conexoes = 1024,
        };
    }

    fn imprimir(self: Config) void {
        std.debug.print("=== Configuração ===\n", .{});
        std.debug.print("  Host: {s}\n", .{self.host});
        std.debug.print("  Porta: {d}\n", .{self.porta});
        std.debug.print("  Modo: {s}\n", .{@tagName(self.modo)});
        std.debug.print("  Log: {s}\n", .{@tagName(self.log_level)});
        std.debug.print("  Max conexões: {d}\n", .{self.max_conexoes});
    }
};

Logging Estruturado

Em produção, logs devem ser em formato JSON para fácil parsing:

const std = @import("std");

const LogLevel = enum(u8) {
    debug = 0,
    info = 1,
    warn = 2,
    erro = 3,

    fn texto(self: LogLevel) []const u8 {
        return switch (self) {
            .debug => "DEBUG",
            .info => "INFO",
            .warn => "WARN",
            .erro => "ERROR",
        };
    }
};

const Logger = struct {
    nivel_minimo: LogLevel,
    writer: std.fs.File.Writer,

    fn init(nivel: LogLevel) Logger {
        return .{
            .nivel_minimo = nivel,
            .writer = std.io.getStdErr().writer(),
        };
    }

    fn log(self: *Logger, nivel: LogLevel, comptime fmt: []const u8, args: anytype) void {
        if (@intFromEnum(nivel) < @intFromEnum(self.nivel_minimo)) return;

        const timestamp = std.time.timestamp();

        // Formato JSON para produção
        self.writer.print(
            "{{\"ts\":{d},\"level\":\"{s}\",\"msg\":\"" ++ fmt ++ "\"}}\n",
            .{timestamp} ++ .{nivel.texto()} ++ args,
        ) catch {};
    }

    fn info(self: *Logger, comptime fmt: []const u8, args: anytype) void {
        self.log(.info, fmt, args);
    }

    fn warn(self: *Logger, comptime fmt: []const u8, args: anytype) void {
        self.log(.warn, fmt, args);
    }

    fn erro(self: *Logger, comptime fmt: []const u8, args: anytype) void {
        self.log(.erro, fmt, args);
    }

    fn debug(self: *Logger, comptime fmt: []const u8, args: anytype) void {
        self.log(.debug, fmt, args);
    }
};

Graceful Shutdown

Em produção, o servidor deve desligar graciosamente, completando requisições em andamento:

const std = @import("std");

const ServidorProducao = struct {
    rodando: std.atomic.Value(bool),
    conexoes_ativas: std.atomic.Value(u32),

    fn init() ServidorProducao {
        return .{
            .rodando = std.atomic.Value(bool).init(true),
            .conexoes_ativas = std.atomic.Value(u32).init(0),
        };
    }

    fn parar(self: *ServidorProducao) void {
        self.rodando.store(false, .seq_cst);
        std.debug.print("Sinal de parada recebido. Aguardando conexões...\n", .{});

        // Esperar conexões ativas terminarem (timeout de 30s)
        var espera: u32 = 0;
        while (self.conexoes_ativas.load(.seq_cst) > 0 and espera < 30) : (espera += 1) {
            std.time.sleep(std.time.ns_per_s);
            std.debug.print("  Aguardando {d} conexões ativas...\n", .{
                self.conexoes_ativas.load(.seq_cst),
            });
        }

        std.debug.print("Servidor encerrado.\n", .{});
    }

    fn estaRodando(self: *ServidorProducao) bool {
        return self.rodando.load(.seq_cst);
    }

    fn registrarConexao(self: *ServidorProducao) void {
        _ = self.conexoes_ativas.fetchAdd(1, .seq_cst);
    }

    fn liberarConexao(self: *ServidorProducao) void {
        _ = self.conexoes_ativas.fetchSub(1, .seq_cst);
    }
};

Health Check Endpoint

Serviços em produção precisam de endpoints de saúde para load balancers e orquestradores:

const HealthStatus = struct {
    status: []const u8,
    uptime_s: i64,
    memoria_usada_kb: usize,
    conexoes_ativas: u32,
    versao: []const u8,
};

fn healthCheckHandler(
    allocator: std.mem.Allocator,
    servidor: *ServidorProducao,
    inicio_app: i64,
) ![]u8 {
    const agora = std.time.timestamp();
    const uptime = agora - inicio_app;

    const status = HealthStatus{
        .status = "healthy",
        .uptime_s = uptime,
        .memoria_usada_kb = 0, // Em produção, usar /proc/self/status
        .conexoes_ativas = servidor.conexoes_ativas.load(.seq_cst),
        .versao = "1.0.0",
    };

    var buffer = std.ArrayList(u8).init(allocator);
    try std.json.stringify(status, .{}, buffer.writer());
    return buffer.toOwnedSlice();
}

Dockerfile para Zig

Zig produz binários estáticos que são ideais para containers mínimos:

# Build stage
FROM alpine:3.19 AS builder

# Instalar Zig
RUN apk add --no-cache curl xz
RUN curl -L https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | \
    tar -xJ -C /usr/local && \
    ln -s /usr/local/zig-linux-x86_64-0.13.0/zig /usr/local/bin/zig

WORKDIR /app
COPY . .

# Compilar em modo release
RUN zig build -Doptimize=ReleaseSafe

# Runtime stage - imagem mínima
FROM scratch

# Copiar apenas o binário
COPY --from=builder /app/zig-out/bin/servidor /servidor

# Expor porta
EXPOSE 8080

# Executar
ENTRYPOINT ["/servidor"]

A imagem final terá apenas o binário — geralmente menos de 1 MB.

docker-compose.yml

version: '3.8'

services:
  api:
    build: .
    ports:
      - "8080:8080"
    environment:
      - APP_MODE=producao
      - PORT=8080
      - HOST=0.0.0.0
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 128M
          cpus: '0.5'
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3

Build System para Produção

Configure seu build.zig com modos de otimização:

const std = @import("std");

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

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

    // Strip em produção para binário menor
    if (optimize != .Debug) {
        exe.root_module.strip = true;
    }

    b.installArtifact(exe);

    // Comando de run
    const run_cmd = b.addRunArtifact(exe);
    const run_step = b.step("run", "Executar o servidor");
    run_step.dependOn(&run_cmd.step);

    // Testes
    const tests = b.addTest(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    const test_step = b.step("test", "Executar testes");
    test_step.dependOn(&b.addRunArtifact(tests).step);
}

Compilar para produção:

# ReleaseSafe: otimizado + safety checks (recomendado)
zig build -Doptimize=ReleaseSafe

# ReleaseFast: máxima performance (sem safety checks)
zig build -Doptimize=ReleaseFast

# ReleaseSmall: menor binário possível
zig build -Doptimize=ReleaseSmall

# Cross-compile para Linux (de qualquer SO)
zig build -Dtarget=x86_64-linux -Doptimize=ReleaseSafe

Checklist de Produção

Antes de fazer deploy, verifique:

  • Variáveis de ambiente para todas as configurações
  • Logging em JSON para stdout/stderr
  • Health check endpoint funcionando
  • Graceful shutdown implementado
  • Rate limiting ativo
  • CORS configurado corretamente
  • Testes passando em modo release
  • Binário compilado com ReleaseSafe
  • Docker image construída e testada
  • Monitoramento de memória (Zig não tem GC — monitore leaks)

Conclusão da Série

Ao longo desta série, construímos uma aplicação web completa em Zig:

  1. Servidor HTTP — Fundamentos de networking
  2. Roteamento — Organização modular de endpoints
  3. API REST — CRUD com JSON e validação
  4. Middlewares — Logging, CORS, auth e rate limiting
  5. Produção — Deploy com Docker e melhores práticas

Zig oferece uma combinação única de performance e controle que a torna excelente para serviços que precisam de latência ultra-baixa e uso mínimo de recursos.

Leitura Complementar

Continue aprendendo Zig

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