Documentação do app v1.0

Variáveis de ambiente

Esta é a lista completa das variáveis que o backend do Load Balance lê do ambiente, com o default exato de cada uma, se o boot falha sem ela e o que ela controla. Use como referência de configuração e para auditar um deploy existente.

Como o boot valida#

Todas as variáveis passam por um schema Zod único, executado por validateEnv() no próprio import do módulo de configuração. Uma variável fora do schema não é lida; uma variável dentro do schema com valor inválido derruba o processo. Quando o ZodError acontece, a mensagem nomeia cada uma das variáveis culpadas.

text
Environment validation failed:
  - DATABASE_URL: DATABASE_URL is required

Depois do parse, o código roda travas condicionais ao ambiente. Estas são as três que mudam o comportamento do boot:

  • DATABASE_URL é a única sem default: o Zod recusa string vazia em qualquer ambiente.
  • Sete variáveis do login pela Conta Nommand são checadas em bloco e a falha nomeia só as que faltam: OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_REDIRECT_URI, OIDC_POST_LOGOUT_REDIRECT_URI, CONTA_PROVISIONING_SECRET e APP_PUBLIC_URL. Quatro delas ainda precisam ser URL http(s) válida.
  • Em produção, AUTH_DISABLED=true, COOKIE_SECRET vazia ou curta, e CORS_ORIGINS vazia derrubam o boot.
  • Por que throw e não warn

    O motivo está no código: operadores esquecem env vars e o serviço sobe silenciosamente aberto. Um aviso passaria despercebido; um erro de boot aparece no log do orquestrador. Os testes de integração, por outro lado, não derrubam o processo: sem as variáveis TENANCY_IT_* eles rodam assim mesmo e o gate de skips é quem reprova.

    Banco, servidor e infra#

    A conexão com o Postgres, o cliente Redis, o pool e a escuta do servidor.

    VariávelDefaultObrigatóriaO que controla
    DATABASE_URL—SimdatasourceUrl do Prisma, com os parâmetros de pool injetados. Sem ela o boot falha em qualquer ambiente.
    REDIS_URLredis://localhost:6379NãoCliente Redis.
    DB_CONNECTION_LIMIT2000Nãoconnection_limit do Prisma, por instância. Decisão explícita do dono: nunca reduzir — 50 saturava com mais de 100 agentes simultâneos.
    DB_POOL_TIMEOUT30 (s)Nãopool_timeout: quanto o Prisma espera por uma conexão livre antes de lançar.
    PORT3018NãoPorta do app.listen. Uma porta reservada pelo sistema é sobrescrita para 3018 com aviso no boot.
    NODE_ENVproductionNãodevelopment, production ou test. Decide as travas de segurança.

    Portas reservadas que o servidor não aceita: 22, 3333, 5173, 5900, 6080 e 8443.

    Dimensione o Postgres pela soma

    O max_connections do Postgres tem de comportar o limite de cada instância somado — multiplicado por 2 durante o deploy, quando a antiga e a nova convivem — mais os outros apps do mesmo servidor e uma margem para sessões admin.

    Limites de payload e mídia#

    O proxy aceita do cliente muito mais do que envia ao upstream, de propósito: ele precisa receber o body inteiro para podar a mídia antiga em vez de devolver 413.

    VariávelDefaultObrigatóriaO que controla
    BODY_LIMIT_MB200NãobodyLimit do Fastify: o que o proxy aceita do cliente. Fica bem acima do limite do upstream porque sessões de agente acumulam screenshots e passam dos 32 MB.
    UPSTREAM_MAX_BODY_MB30NãoO que o proxy envia ao upstream. Acima disso, as mídias mais antigas do histórico viram placeholder de texto.
    UPSTREAM_MAX_IMAGES100NãoTeto de imagens por request — a Anthropic recusa mais de 100 com 413/400.
    IMAGE_MAX_BASE64_MB5NãoTeto do base64 de uma imagem depois do resize.
    IMAGE_RESIZE_MAX_EDGE_PX1568NãoLado maior da redução de imagem. O valor 0 desliga o resize. 1568 px é o ponto a partir do qual a Anthropic já reduz do lado dela.
    IMAGE_RESIZE_CACHE_MB128NãoTamanho do cache de resize, em bytes de imagem.
    WS_MAX_PAYLOAD_MB10NãomaxPayload do WebSocket /ws/requests. 1 MB derruba lotes grandes sob alta concorrência.

    Proxy: cooldown, failover e streaming#

    As variáveis que decidem quando uma chave sai da frente, quando volta e quando o pedido migra para a próxima.

    VariávelDefaultObrigatóriaO que controla
    BILLING_COOLDOWN_MS60000NãoPausa da chave depois de um 402. Um 402 nunca desativa a chave: ela só sai do rodízio por esta janela e volta sozinha.
    BILLING_RETRY_AFTER_SEC300NãoValor do header Retry-After em cobrança esgotada.
    FIRST_CHUNK_TIMEOUT_MS15000
    (faixa 1000–120000)
    NãoEspera pelo primeiro frame SSE válido antes de dar failover. Sem ela o proxy esperaria o timeout inteiro do upstream para decidir.
    HEDGE_DELAY_MS2000
    (faixa 500–30000)
    NãoAtraso da reserva do hedge antes do primeiro token.
    STREAM_LOG_LIMIT_BYTES1048576
    (mín. 50000)
    NãoTeto do coletor SSE. Acima de 50 KB um frame de erro que chega tarde era truncado antes de o coletor conseguir ver.
    REASONING_OWNER_TTL_SEC86400NãoJanela do registro que guarda qual chave cifrou cada bloco de raciocínio. Expirar só custa o replay do raciocínio.
    AUTO_DISABLE_ON_ERRORtrueNãoKill-switch do auto-disable de chave em erro não-2xx. Com false o proxy ainda sanitiza a resposta, mas nenhuma chave é desativada.
    SAME_ERROR_GUARD_THRESHOLD3NãoSe o mesmo par status + mensagem atinge N chaves dentro de uma request, os auto-disables daquela request são revertidos.

    O guard existe por um motivo escrito no código: um nome de modelo digitado errado não pode derrubar 600 chaves de uma vez.

    Rate limit e endereço do cliente#

    VariávelDefaultObrigatóriaO que controla
    RATE_LIMIT_PROXY_IP_PER_MIN100NãoRate limit do proxy por IP, por minuto.
    RATE_LIMIT_PROXY_URL_PER_MIN500NãoRate limit do proxy por URL personalizada, por minuto.
    TRUST_PROXY_HOPS1NãoQuantos saltos de proxy à frente são confiáveis. Com trustProxy: n o X-Forwarded-For injetado além do salto confiável é ignorado, então um atacante não forja o bucket do rate limit girando o header. 0 desliga a confiança; em produção isso gera aviso no boot.

    Autenticação e sessão#

    A sessão do painel e o segredo que assina o cookie e o token CSRF.

    VariávelDefaultObrigatóriaO que controla
    AUTH_DISABLEDfalseSim em produção ser falseCom true desliga toda a autenticação admin. true em produção derruba o boot.
    SESSION_COOKIE_NAMEsession_idNãoNome do cookie de sessão.
    COOKIE_SECRET""Sim em produçãoAssinatura do cookie de sessão e do token CSRF. Vazia em produção derruba o boot; curta demais (COOKIE_SECRET_MIN_LENGTH = 32) também. Fora de produção, vazia deriva o segredo do DATABASE_URL e o boot avisa — o GET /api/health expõe o modo como "env" ou "derived", nunca o valor.
    COOKIE_SECUREausente ⇒ NODE_ENV === 'production'NãoFlag Secure do cookie. Sem valor explícito, desenvolvimento em HTTP local consegue gravar o cookie.
    COOKIE_SAMESITElaxNãolax, strict ou none. strict quebra o callback OAuth que volta de outro domínio; none exige COOKIE_SECURE=true, senão o navegador rejeita o cookie.
    Trocar o COOKIE_SECRET derruba as sessões abertas

    Configurar ou trocar o valor invalida uma vez todos os cookies assinados com o segredo anterior. O aviso está no comentário da própria variável.

    OIDC / Conta Nommand#

    O login é só pela Conta Nommand: não existe login próprio nem gestão de empresa no app. As sete variáveis abaixo são obrigatórias em qualquer ambiente — o boot falha nomeando as que faltam.

    VariávelDefaultObrigatóriaO que controla
    OIDC_ISSUER""SimIssuer da Conta, sem barra no fim. A discovery sai do issuer + /.well-known/openid-configuration. Precisa ser URL http(s).
    OIDC_CLIENT_ID""Simclient_id do app na Conta — o slug loadbalance também é a chave de apps[...] no id_token.
    OIDC_CLIENT_SECRET""SimSegredo do cliente confidencial (client_secret_basic). Nunca vai para log.
    OIDC_REDIRECT_URI""SimURL exata do callback cadastrada na Conta: o app + /auth/callback. Precisa ser URL http(s).
    OIDC_POST_LOGOUT_REDIRECT_URI""SimPara onde a Conta volta depois do logout: o app + /auth/logged-out. Precisa ser URL http(s).
    CONTA_PROVISIONING_SECRET""SimHMAC-SHA256 do webhook de provisionamento, verificado no header X-Conta-Signature.
    APP_PUBLIC_URL""SimOrigem pública do frontend. Precisa ser URL http(s); barra final é removida no boot.
    OIDC_REFRESH_INTERVAL_SEC600
    (clamp 30–600)
    NãoIntervalo do refresh da sessão na Conta. O padrão de ≤ 10 min pega troca de papel e perda de acesso mesmo sem back-channel.
    A mensagem de falha nomeia o que falta

    O erro do boot sai na forma [AUTH] O login é pela Conta Nommand e exige OIDC_ISSUER, APP_PUBLIC_URL. Configure as variáveis da Conta Nommand. Recusando o boot. — a lista é montada a partir das variáveis que vieram vazias, não é uma lista fixa.

    CORS e log#

    VariávelDefaultObrigatóriaO que controla
    CORS_ORIGINS""Sim em produçãoOrigens permitidas, separadas por vírgula. Vazia em produção derruba o boot; em development e test a ausência permite todas.
    CORS_ALLOW_ANYfalseNãoEscape hatch explícito: true força allow-all e imprime aviso. Não é recomendado.
    LOG_LEVELinfoNãoNível do logger Pino: fatal, error, warn, info, debug, trace ou silent.
    LOG_ENABLEDfalseNãoEm produção o logger do Fastify é OFF por padrão. true liga o JSON estruturado sem mudar outro comportamento; em desenvolvimento o log é sempre ligado.
    DEBUG_LOGGINGfalseNãoLog verboso.
    CORS_ALLOW_ANY=true em produção é risco real

    O servidor aceita a combinação allow-all com credenciais porque o aviso de app.ts a chama de silent open-CORS + admin-data-exfiltration. A alternativa honesta é listar as origens em CORS_ORIGINS.

    Segredos em repouso e OAuth#

    A cifragem das credenciais de provedor guardadas no banco e os fluxos OAuth contra o Anthropic e o GitHub.

    VariávelDefaultObrigatóriaO que controla
    KEY_ENCRYPTION_KEY""Não, mas é o que ativa a cifragemBase64 de 32 bytes para AES-256-GCM de ApiKey.key, ApiKey.refreshToken e dos tokens dentro de ApiKey.metadata. Gerada com openssl rand -base64 32. Vazia ⇒ as credenciais são gravadas em texto puro, com aviso no boot. Presente e inválida ⇒ o boot falha em qualquer ambiente.
    KEY_ENCRYPTION_KEY_PREVIOUS""NãoChave anterior, só para rotação: linhas cifradas com a chave velha continuam legíveis depois da troca. Passa pela mesma validação de 32 bytes.
    OAUTH_CLIENT_ID""Nãoclient_id do refresh contra console.anthropic.com/v1/oauth/token. Vazio ⇒ o fluxo OAuth da Anthropic é desligado e o boot avisa. Não existe default de produção de propósito.
    OAUTH_CLIENT_SECRETausente — sem defaultNãoSegredo do fluxo OAuth da Anthropic. Sem default no schema.
    OAUTH_REDIRECT_URIhttps://console.anthropic.com/oauth/code/callbackNãoRedirect do fluxo Anthropic.
    GITHUB_CLIENT_ID""NãoVazio desliga o fluxo GitHub OAuth — a string vazia é falsy e o gate do fluxo é !env.GITHUB_CLIENT_ID.
    GITHUB_CLIENT_SECRET""NãoSegredo do fluxo GitHub OAuth; combinado com o GITHUB_CLIENT_ID no mesmo gate.
    GITHUB_REDIRECT_URI""NãoCallback do fluxo GitHub.
    Perder a KEY_ENCRYPTION_KEY perde todas as chaves de provedor

    O aviso está escrito literalmente no schema: não há recuperação. Guarde a chave em cofre e use a mesma em todas as instâncias antes de rodar o backfill. Use KEY_ENCRYPTION_KEY_PREVIOUS para trocar a chave sem deixar as linhas antigas ilegíveis.

    Com a chave presente, cada valor gravado leva o formato enc:v1:kid:iv:tag:ct e um AAD que amarra o valor à linha e ao campo (loadbalance:apikey:id:campo). Se a chave faltar no boot, assertSecretsReadable() varre a tabela api_keys e derruba o processo citando o id da linha, nunca o valor.

    Retenção e limpeza#

    Cada janela tem o próprio timer ou o próprio lote. O audit não tem intervalo próprio: ele roda dentro do timer de 6 h do cleanup, com a janela dele.

    VariávelDefaultObrigatóriaO que controla
    RETENTION_DAYS30NãoJanela de requests.
    AUDIT_RETENTION_DAYS365NãoAudit log — responde “quem fez isso” meses depois, por isso vive bem mais que os requests.
    REQUEST_PAYLOAD_RETENTION_HOURS72NãoCorpo gzip e metadata completa do request. Separada da RETENTION_DAYS porque o payload é caro e só serve para a tela de detalhe.
    REQUEST_METRICS_ROLLUP_LOOKBACK_HOURS24NãoJanela que o job de rollup cobre desde o boot. É informativo: o job é idempotente e não guarda estado.
    CLEANUP_BATCH_SIZE1000NãoTamanho do lote de DELETE do cleanup. Reduza se o lote causar contenção de lock.
    COMPRESSION_GZIP_LEVEL6NãoNível de gzip dos corpos armazenados, de 0 (rápido) a 9 (menor volume).

    Observabilidade e alerta#

    Cada integração só inicializa quando a variável dela está setada — sem import, sem hook, zero overhead quando não configurada. Se a variável aponta para um SDK que não está instalado, o boot registra uma linha de aviso e o app segue normal.

    VariávelDefaultObrigatóriaO que controla
    SENTRY_DSN""NãoVazia desliga o Sentry: a inicialização faz early-return.
    SENTRY_ENVIRONMENT"" ⇒ NODE_ENVNãoenvironment do Sentry.
    SENTRY_TRACES_SAMPLE_RATE0NãoFração de transações rastreadas. 0 reporta só erros, sem tracing.
    OTEL_EXPORTER_OTLP_ENDPOINT""NãoEndpoint OTLP/HTTP de traces. Vazio desliga o OpenTelemetry: a inicialização faz early-return.
    OTEL_SERVICE_NAMEai-proxy-loadbalancerNãoserviceName enviado ao coletor OTel.
    ALERT_WEBHOOK_URL""NãoVazio desliga o alerta. Com URL, o proxy dispara em quatro eventos: trip do [GUARD], circuit-breaker OAuth em OPEN, falha permanente de OAuth e auto-disable de chave. O payload é aceito por Slack incoming webhooks e PagerDuty Events API v2.
    ALERT_DEDUP_MS30000NãoJanela de dedup por par evento + chave. O primeiro evento da janela dispara, os seguintes são coalescidos; 0 desliga o dedup.

    Só de teste e fora do schema#

    Estas variáveis não são lidas pelo backend em execução. Elas aparecem no backend/.env.example, que é documento e não configuração.

    VariávelDefaultObrigatóriaO que controla
    TENANCY_IT_DATABASE_URL—Só para npm run test:itBanco descartável da integração. Sem ela os arquivos de segurança abrem com describe.skipIf e o npm test sai 0 com todos eles pulados.
    TENANCY_IT_REDIS_URL—Só para npm run test:itRedis do mesmo suite. Opcional para o script, mas o gate de skips reprova com ela ausente também.
    ADMIN_API_KEY—NãoObsoleta. Saiu do schema; se ainda existir no deploy, é ignorada. Só sessão de usuário autentica.

    Próximos passos#

    Você tem o mapa completo do que o ambiente controla. Para ver cada uma dessas variáveis em ação num deploy real, e a ordem de boot que respeita, siga para o guia de operação.

    Depois de dominar o básico, explore além:

    Documentação gerada com a skill docs-padrao.