Imagem Docker e compose reproduzíveis para homologação
Não existia nenhum artefato de deploy: sem Dockerfile, sem compose, sem pin de Node. O runbook pedia `npm run start` e `npm run worker` à mão, o que não é um ambiente reproduzível, e o worker dependia de `tsx`, uma devDependency, que `npm ci --omit=dev` removeria. - Dockerfile multistage. Web e worker saem da mesma imagem: mesmo código, mesmas dependências, só o subcomando difere. - `tsx` foi para `dependencies`. Conferido em diretório limpo: os 13 pacotes externos que src e scripts usam existem no install de produção. - A sobreposição das duas árvores de node_modules é segura e foi medida: 318 pacotes em produção contra 153 traçados, zero divergência de versão entre os compartilhados, e 3 que existem só na traçada, que o COPY mescla sem remover. O package.json do standalone não é sobrescrito. - Entrypoint com subcomandos, que garante `node --import tsx` em todos os scripts. Usar `node` direto convidaria de volta o defeito do SIGTERM. - Healthcheck de liveness na web e de heartbeat no worker. - Compose local reproduz a topologia com Postgres e Redis de verdade. Compose de homologação não declara Postgres (é o banco gerenciado), usa Redis isolado com senha e volume próprios, publica porta só em 127.0.0.1 e tem nome de projeto, rede e volume exclusivos. Nenhum serviço de outro aplicativo do ecossistema é declarado, tocado ou reiniciado. - `.env.staging.example` sem segredo, `.nvmrc`, `engines` e `packageManager`. - `config/staging-seed.json` e `.env.staging` no gitignore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ca8a1112f0
commit
dea510c3b4
11 files changed
+598
-28
No files matched your search
@@ -0,0 +1,23 @@
|
||||
// Healthcheck do contêiner do worker. O worker grava o heartbeat ao fim de cada
|
||||
// tick; se o arquivo envelhecer além da janela, o processo está vivo mas travado,
|
||||
// que é o caso que um healthcheck de PID não detecta.
|
||||
import { stat } from "node:fs/promises";
|
||||
|
||||
const file = process.env.ADAPTER_WORKER_HEARTBEAT ?? "/tmp/adaptador-worker-heartbeat";
|
||||
const tick = Number(process.env.ADAPTER_WORKER_TICK_MS ?? 2000);
|
||||
// Tolera dez ticks ou 60s, o que for maior: um job grande ocupa o tick inteiro.
|
||||
const maxAgeMs = Math.max(tick * 10, 60_000);
|
||||
|
||||
try {
|
||||
const info = await stat(file);
|
||||
const age = Date.now() - info.mtimeMs;
|
||||
if (age > maxAgeMs) {
|
||||
console.error(`heartbeat com ${Math.round(age / 1000)}s, limite ${Math.round(maxAgeMs / 1000)}s`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`heartbeat com ${Math.round(age / 1000)}s`);
|
||||
process.exit(0);
|
||||
} catch {
|
||||
console.error(`heartbeat ausente em ${file}`);
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in new issue
Block a user