As quatro variáveis fora do .env.example, e por que o APP_BASE_URL de produção não tem /app¶
Motivação¶
Terceira e última ponta solta da revisão de documentação deste dia.
Duas coisas, e a segunda é a que importa. O .env.example da API não listava quatro variáveis que o config.py lê — quem monta ambiente novo não descobre que existem a não ser lendo o código. E o APP_BASE_URL de produção, que termina sem o sufixo /app que local e dev-remote têm, parecia defeito: os consumidores concatenam direto (f"{settings.APP_BASE_URL}/reset-password?token={token}"), então um valor errado geraria link de e-mail quebrado em produção.
Não é defeito, e a razão merecia estar escrita onde alguém a encontre. A produção serve o app na raiz — builda com VITE_BASE_PATH=/ (infra/prd/web.Dockerfile:23) e o Caddy de lá manda / para o app —, ao contrário de local e dev-remote, que servem sob /app. O valor sem sufixo está certo justamente porque o ambiente é diferente.
O que torna isso digno de comentário é como esse erro falharia: em silêncio. O servidor devolve 200 com a casca da SPA para qualquer path, e quem não casa a rota é o React Router, já no navegador de quem recebeu o e-mail. Não há log, não há 404, não há alerta.
Comportamento atual¶
config.pylêPASSWORD_RESET_TOKEN_EXPIRE_MINUTES,PASSWORD_RESET_MAX_REQUESTS,MAX_UPLOAD_SIZE_MBeBCRYPT_ROUNDS; nenhuma aparece no.env.example.- O comentário de
APP_BASE_URLnoconfig.pyafirmava que "o app é servido sob /app na mesma origem da API", o que vale para local e dev-remote e não para produção.
Comportamento desejado¶
O .env.example lista o que o código lê, e a diferença de base entre produção e os outros ambientes está escrita nos dois lugares onde alguém a procuraria.
Escopo¶
apps/api/.env.example, o comentário de APP_BASE_URL em apps/api/app/core/config.py, e o item correspondente da dívida técnica no ROADMAP.md.
Fora do escopo¶
BCRYPT_ROUNDS entrou comentada, de propósito. Expô-la como linha ativa num arquivo de exemplo convida a baixá-la, e baixar o custo do bcrypt em ambiente que guarda senha de verdade é regressão de segurança. Quem a baixa para 4 é o conftest da suíte, e só lá.
Alterações propostas¶
apps/api/.env.example— as três variáveis benignas, maisBCRYPT_ROUNDScomentada com o aviso, e o bloco de comentário explicando a base por ambiente.apps/api/app/core/config.py— só comentário, nenhuma linha de código: o deAPP_BASE_URLpassa a dizer que o sufixo não é universal, citarinfra/prd/web.Dockerfile:23e nomear o modo de falha silenciosa.ROADMAP.md— o item sai da lista de dívida aberta e entra na de resolvidos, com a explicação. A redação anterior, escrita horas antes neste mesmo dia, dizia "conferir antes de confiar" e deixava no ar a suspeita de defeito.
Impactos¶
Nenhum no produto. O diff em config.py foi conferido linha a linha e é exclusivamente comentário — nada de valor default, nada de comportamento —, então este ajuste não publica versão nem tag.
Validação¶
make preflight — limpo. ruff check no config.py real (copiado para o container, porque a imagem embute o código e checar o container mediria a versão anterior) — passou; nenhuma linha acrescentada passa de 120 colunas.
A afirmação central foi medida, não deduzida: VITE_BASE_PATH=/ em infra/prd/web.Dockerfile:23, o comentário do infra/prd/Caddyfile dizendo que só /api* desvia, e curl na produção confirmando que a raiz responde. O /app/... também devolve 200 — e é exatamente por isso que o erro seria silencioso, o que reforça o comentário em vez de contradizê-lo.
Documentos relacionados¶
ADR-0003 — a origem única roteada por path, que é a razão de local e dev-remote servirem sob /app. Os dois tweaks anteriores deste dia, de que este é a continuação.