---
title: "Zig com systemd: Serviço Linux em Produção"
url: "https://ziglang.com.br/artigos/zig-systemd-servico-linux-producao/"
markdown_url: "https://ziglang.com.br/artigos/zig-systemd-servico-linux-producao.MD"
description: "Como empacotar e operar um binário Zig como serviço systemd no Linux: unit file, usuário dedicado, variáveis de ambiente, logs, restart, hardening e checklist de deploy."
date: "2026-05-31"
author: ""
---

# Zig com systemd: Serviço Linux em Produção

Como empacotar e operar um binário Zig como serviço systemd no Linux: unit file, usuário dedicado, variáveis de ambiente, logs, restart, hardening e checklist de deploy.


Rodar um binário Zig em produção não precisa começar por Kubernetes. Para muitos projetos brasileiros, a primeira operação real acontece em uma VPS Linux, em um servidor de laboratório, em uma máquina industrial, em um host de borda ou em uma instância pequena atrás de Caddy, Nginx ou Cloudflare Tunnel. Nesse cenário, `systemd` continua sendo uma escolha direta: inicia o processo no boot, reinicia em falhas, captura logs, injeta variáveis de ambiente e permite aplicar limites de segurança sem transformar o deploy em uma plataforma inteira.

Zig combina bem com esse modelo porque gera um executável nativo, sem runtime externo e sem árvore grande de dependências. O risco é confundir essa simplicidade com ausência de operação. Um serviço Zig ainda precisa de usuário dedicado, diretórios previsíveis, configuração clara, logs legíveis, resposta correta a `SIGTERM`, health check, política de restart e um caminho de rollback. O binário pode ser pequeno; o contrato operacional não deve ser improvisado.

Este guia mostra uma abordagem prática para publicar uma aplicação Zig como **serviço systemd no Linux**. Ele complementa os textos sobre [servidor HTTP em produção](/artigos/zig-http-server-producao/), [configuração segura com variáveis de ambiente](/artigos/zig-configuracao-segura-segredos-env/), [observabilidade em Zig](/artigos/zig-observabilidade/), [Docker e containers](/artigos/zig-docker-containers/) e [ferramentas internas para DevOps](/artigos/zig-ferramentas-internas-devops/). Se o seu time também mantém serviços em Go, vale comparar o contrato operacional com referências de ecossistema como [golang.com.br](https://golang.com.br/), porque o desenho de logs, sinais e deploy costuma ser parecido mesmo quando a linguagem muda.

## Quando systemd é suficiente

Use `systemd` quando você precisa operar um ou poucos processos em um host Linux conhecido. É um bom encaixe para APIs pequenas, workers internos, agentes locais, coletores, servidores TCP, ferramentas de automação que ficam residentes e serviços que não exigem escalonamento horizontal imediato.

Ele deixa de ser suficiente quando a aplicação precisa de scheduling complexo, autoscaling, isolamento multi-tenant, service discovery dinâmico, rollout progressivo entre dezenas de réplicas ou gestão declarativa de muitos hosts. Nesses casos, containers orquestrados podem fazer sentido. Mas não pule para Kubernetes só porque "produção" parece sinônimo de cluster. Uma unit bem escrita, monitoramento simples e rollback documentado resolvem muita coisa.

## Estrutura de arquivos no servidor

Evite rodar o binário a partir do diretório do usuário que fez o deploy. Escolha uma estrutura explícita:

```text
/opt/minha-api/
  bin/minha-api
  releases/2026-05-31-0410/minha-api
  shared/
    data/
    cache/
    tmp/
/etc/minha-api/
  minha-api.env
/var/log/minha-api/       # opcional; journalctl pode bastar
```

Um padrão simples é manter `/opt/minha-api/bin/minha-api` como symlink para a versão atual em `releases/`. O rollback vira trocar o symlink e reiniciar o serviço. Para aplicações pequenas, copiar o binário direto para `/opt/minha-api/bin/` também funciona, desde que você saiba voltar para a versão anterior.

Crie um usuário sem login para executar o processo:

```bash
sudo useradd --system --home /opt/minha-api --shell /usr/sbin/nologin minha-api
sudo mkdir -p /opt/minha-api/bin /opt/minha-api/shared/data /etc/minha-api
sudo chown -R minha-api:minha-api /opt/minha-api
sudo chmod 750 /opt/minha-api
```

Não rode o serviço como `root` só porque ele precisa abrir porta baixa ou acessar um arquivo. Prefira usar proxy reverso na porta 80/443, ajustar permissões específicas ou conceder capacidades mínimas quando for inevitável.

## Compile um binário previsível

No ambiente de build, gere um executável otimizado e copie somente o artefato necessário:

```bash
zig build -Doptimize=ReleaseSafe
install -m 0755 zig-out/bin/minha-api /tmp/minha-api
```

`ReleaseSafe` mantém checks de segurança úteis, enquanto reduz bastante o custo em relação ao modo debug. Para um serviço exposto à rede, essa costuma ser uma primeira escolha melhor que `ReleaseFast`, a menos que você tenha benchmark e perfil de risco bem definidos. Se o binário depende de arquivos externos, templates ou migrações, trate esses itens como parte do release, não como detalhe manual.

Registre a versão no próprio binário quando possível. Uma rota `/version`, um comando `--version` ou uma linha de boot como `version=2026-05-31 git=abc123` ajuda muito na hora de conferir se o host está rodando o que você acabou de publicar.

## Unit file mínimo

Um arquivo `/etc/systemd/system/minha-api.service` pode começar assim:

```ini
[Unit]
Description=Minha API Zig
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=minha-api
Group=minha-api
WorkingDirectory=/opt/minha-api
EnvironmentFile=/etc/minha-api/minha-api.env
ExecStart=/opt/minha-api/bin/minha-api
Restart=on-failure
RestartSec=3s
TimeoutStopSec=20s
KillSignal=SIGTERM

[Install]
WantedBy=multi-user.target
```

Esse unit file já dá um contrato melhor que iniciar o processo com `nohup` ou `screen`. `Restart=on-failure` cobre crash e exit code inesperado. `TimeoutStopSec` dá tempo para o serviço encerrar conexões e liberar recursos antes do `systemd` partir para kill mais forte. `EnvironmentFile` separa configuração do binário.

O arquivo de ambiente pode ser assim:

```bash
PORT=8080
LOG_LEVEL=info
DATABASE_PATH=/opt/minha-api/shared/data/app.sqlite
PUBLIC_BASE_URL=https://minha-api.exemplo.com.br
```

Não coloque segredos nesse arquivo se ele for gerenciado por um processo frouxo de backup, chat ou wiki. Quando houver tokens, limite permissão com `chmod 640`, dono `root:minha-api`, e documente onde a fonte do segredo vive. O artigo de [configuração segura em Zig](/artigos/zig-configuracao-segura-segredos-env/) cobre essa fronteira com mais detalhes.

## Código Zig preparado para systemd

O serviço precisa escrever logs em `stdout` ou `stderr`, carregar configuração no boot e responder a `SIGTERM`. Para um servidor HTTP, a ideia é não aceitar trabalho novo depois do sinal e dar tempo para requisições em andamento terminarem.

Um esqueleto reduzido:

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

var stopping = std.atomic.Value(bool).init(false);

fn handleTerm(_: c_int) callconv(.C) void {
    stopping.store(true, .seq_cst);
}

pub fn main() !void {
    const action = posix.Sigaction{
        .handler = .{ .handler = handleTerm },
        .mask = posix.empty_sigset,
        .flags = 0,
    };
    try posix.sigaction(posix.SIG.TERM, &action, null);
    try posix.sigaction(posix.SIG.INT, &action, null);

    std.log.info("service_start port={d}", .{8080});

    while (!stopping.load(.seq_cst)) {
        // aceite conexões, processe fila ou execute o loop principal
        std.time.sleep(100 * std.time.ns_per_ms);
    }

    std.log.info("service_stop reason=signal", .{});
}
```

Esse exemplo não substitui uma arquitetura completa, mas fixa o ponto principal: sinal não é exceção misteriosa. É parte normal da operação. O conteúdo de [signals em Zig](/receitas/zig-sinais-signals/) aprofunda o assunto, e o guia de [filas e workers](/artigos/zig-filas-workers-background/) mostra por que encerramento limpo importa para tarefas em background.

## Logs com journalctl

Se o processo escreve em `stdout` e `stderr`, o `systemd` captura automaticamente:

```bash
sudo journalctl -u minha-api.service -f
sudo journalctl -u minha-api.service --since "1 hour ago"
sudo systemctl status minha-api.service
```

Prefira logs de uma linha por evento, com campos estáveis. Exemplo:

```text
level=info event=request_done method=GET path=/healthz status=200 duration_ms=3
level=error event=db_open_failed path=/opt/minha-api/shared/data/app.sqlite error=AccessDenied
```

Não dependa de cor, barra de progresso ou mensagens interativas. Serviço Linux não é sessão de terminal. Logs precisam sobreviver a cópia, agregação e busca por texto.

## Hardening sem exagero

Depois que o serviço sobe, aplique algumas proteções no unit file:

```ini
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/minha-api/shared
CapabilityBoundingSet=
LockPersonality=true
MemoryDenyWriteExecute=true
```

`ProtectSystem=strict` torna o sistema de arquivos majoritariamente somente leitura para o processo. `ReadWritePaths` libera apenas o diretório onde a aplicação realmente precisa gravar. `NoNewPrivileges` reduz risco de escalada. `PrivateTmp` isola `/tmp`. Nem toda aplicação aceita todos esses limites de primeira, então aplique incrementalmente e valide.

Se o serviço precisa bindar em porta 80 diretamente, você pode usar `AmbientCapabilities=CAP_NET_BIND_SERVICE`, mas geralmente é melhor deixar Caddy ou Nginx na frente e manter a aplicação Zig em `127.0.0.1:8080`.

## Deploy e rollback

Um fluxo manual seguro:

```bash
sudo install -m 0755 minha-api /opt/minha-api/releases/2026-05-31-0410/minha-api
sudo ln -sfn /opt/minha-api/releases/2026-05-31-0410/minha-api /opt/minha-api/bin/minha-api
sudo systemctl daemon-reload
sudo systemctl restart minha-api.service
sudo systemctl status minha-api.service
curl -fsS http://127.0.0.1:8080/healthz
```

Rollback:

```bash
sudo ln -sfn /opt/minha-api/releases/2026-05-30-2215/minha-api /opt/minha-api/bin/minha-api
sudo systemctl restart minha-api.service
curl -fsS http://127.0.0.1:8080/healthz
```

O importante é validar depois do restart. `systemctl restart` retornar zero não prova que a aplicação está saudável; prova apenas que o comando foi aceito. A rota de health check, os logs e uma requisição real precisam confirmar o deploy.

## Checklist final

Antes de chamar o serviço de produção, confira:

- o binário foi compilado com versão de Zig conhecida;
- o serviço roda com usuário dedicado, não como `root`;
- configuração obrigatória falha no boot com mensagem clara;
- segredos não aparecem em logs, argumentos de processo ou Git;
- `SIGTERM` encerra o loop principal com limpeza mínima;
- `Restart=on-failure` está configurado com atraso pequeno;
- logs aparecem em `journalctl -u nome.service`;
- diretórios de escrita são explícitos;
- hardening foi aplicado e testado;
- existe rollback simples para a versão anterior;
- uma rota ou comando de health/version confirma o release ativo.

A vantagem de Zig nesse ambiente é tornar o deploy pequeno e audível. Você sabe qual binário está rodando, qual usuário executa, quais arquivos ele pode tocar e como ele termina. `systemd` não é glamour de plataforma, mas é uma base madura para serviços Linux simples. Para muitos produtos, essa clareza vale mais que uma arquitetura distribuída prematura.
