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:
- Servidor HTTP — Fundamentos de networking
- Roteamento — Organização modular de endpoints
- API REST — CRUD com JSON e validação
- Middlewares — Logging, CORS, auth e rate limiting
- 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
- Otimização de Performance — Otimize seu servidor
- Testes Avançados — CI/CD para Zig
- Programação de Sistemas — Networking avançado
- Masterclass de Memória — Evite leaks em produção