mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 01:28:34 +08:00
O serviço `worker` do compose de produção não tinha `image:`, só `build:`. As
consequências, todas medidas, encadeiam:
1. `docker compose pull` PULA serviço build-only ("Skipped - No image to be
pulled"), então o `dc pull` do install/update nunca o trazia;
2. `up -d` sem `--build` recria o contêiner sobre a imagem velha — provei num
compose stub: Dockerfile alterado, `up -d`, e o contêiner segue imprimindo
VERSAO-1;
3. logo o worker era construído na VPS de cada cliente (pnpm install de 82
pacotes) e NUNCA mais atualizado.
E ele não é acessório: `app/api/v1/cron/agent-dispatcher` é no-op permanente, o
agent-worker é o único consumidor de `ai_agent.dispatch_requested`. O runtime do
agente de IA — a feature-título — era a única peça que não recebia correção. O
QA de instalação real até registrou o sintoma sem tirar a conclusão
("a VPS realmente compila o worker", user-journey-map.md:295).
A mudança é ADITIVA por construção: `build:` continua ao lado de `image:`, e
medi que o Compose constrói localmente quando a imagem não existe no registry —
com e sem pull_policy. Registry fora do ar, ou clone anterior à primeira
publicação, caem no comportamento de hoje em vez de quebrar.
Também:
- scheduler vira imagem e para de rodar `apk add curl tzdata` a cada start. Num
serviço `restart: unless-stopped` isso amarrava a volta do cron à internet da
VPS e ao mirror do Alpine, justamente quando a máquina se recupera de algo;
- WAHA pinado em `latest-2026.7.2` — MESMO digest de `latest` hoje
(65e593e30bb7), então zero mudança de conteúdo; muda quem decide quando o
WhatsApp do cliente troca de motor;
- `srh` pinado por DIGEST, não por tag: `latest` (5b0bb923) e `0.0.10`
(65128347) são imagens DIFERENTES, então `:0.0.10` trocaria o binário do
parque — o risco que a pendência T6 mandava validar antes. O digest congela o
que já roda, sem incorrer nesse risco;
- `/api/v1/health` para de mentir. Lia `npm_package_version`, que é `undefined`
sob `CMD ["node","server.js"]`: toda instalação do mundo reportava "0.1.0". O
fallback agora é "desconhecido" — um campo que responde errado com confiança
desliga a pergunta, e é pior que um campo ausente.
O gate novo tem guarda do próprio instrumento: a primeira versão do teste de
pull_policy passava sem avaliar um único serviço (só cobrava tag imutável, e os
três apontam para tag móvel). Agora ele cobra os dois sentidos e falha se
avaliar menos que os três.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RKm2XcTcfgi1vdMcDYSRWa
84 lines
4.1 KiB
Docker
84 lines
4.1 KiB
Docker
# syntax=docker/dockerfile:1
|
|
# DeskcommCRM — imagem de produção self-host (Next.js standalone).
|
|
# Build: docker build --build-arg NEXT_PUBLIC_SUPABASE_URL=... -t deskcomm-app .
|
|
|
|
# ---- deps: instala dependências (layer cacheável) ----
|
|
FROM node:22-alpine AS deps
|
|
WORKDIR /app
|
|
RUN corepack enable && corepack prepare pnpm@9.15.9 --activate
|
|
COPY package.json pnpm-lock.yaml ./
|
|
RUN pnpm install --frozen-lockfile
|
|
|
|
# ---- build: gera .next/standalone ----
|
|
FROM node:22-alpine AS build
|
|
WORKDIR /app
|
|
RUN corepack enable && corepack prepare pnpm@9.15.9 --activate
|
|
COPY --from=deps /app/node_modules ./node_modules
|
|
COPY . .
|
|
|
|
# IMAGEM GENÉRICA: os NEXT_PUBLIC_* recebem placeholders no build. Os valores
|
|
# REAIS do usuário são injetados em RUNTIME — no browser via <PublicEnvScript/>
|
|
# (window.__PUBLIC_ENV__) e no servidor via lib/env.ts (parseia process.env em
|
|
# runtime). Assim UMA imagem serve qualquer projeto Supabase, sem rebuild.
|
|
# (Segredos de runtime NUNCA entram no build — guarda de fase em lib/env.ts.)
|
|
ARG NEXT_PUBLIC_SUPABASE_URL=https://placeholder.supabase.co
|
|
ARG NEXT_PUBLIC_SUPABASE_ANON_KEY=placeholder-anon-key
|
|
ARG NEXT_PUBLIC_APP_URL=https://placeholder.invalid
|
|
ARG NEXT_PUBLIC_ADMIN_URL=https://placeholder.invalid
|
|
# O build do Next é faminto: o heap default do Node (~2GB) estoura. NODE_OPTIONS
|
|
# eleva pra 4GB. Isso é custo de QUEM BUILDA — o CI —, não de quem instala: o
|
|
# caminho normal do self-hoster é `docker compose pull`, e o install.sh não
|
|
# builda o app. Buildar na VPS é o override opcional de docker-compose.build.yml,
|
|
# e é lá que o requisito de RAM de build se aplica (docs/runbooks/deploy.md §4).
|
|
ENV NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL \
|
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY \
|
|
NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL \
|
|
NEXT_PUBLIC_ADMIN_URL=$NEXT_PUBLIC_ADMIN_URL \
|
|
NODE_ENV=production \
|
|
NEXT_TELEMETRY_DISABLED=1 \
|
|
NODE_OPTIONS=--max-old-space-size=4096
|
|
|
|
# Turbopack (`pnpm build`): ~4min vs ~34min do webpack num VPS. O bloco `webpack:`
|
|
# do Sentry (tree-shake + upload de sourcemap em build-time) é ignorado, mas o
|
|
# Sentry RUNTIME segue ativo (DSN hardcoded nas configs). Sourcemap upload é
|
|
# concern só da Vercel; aqui o ganho de tempo de build é o que importa pro leigo.
|
|
RUN pnpm build
|
|
|
|
# ---- runner: imagem slim de produção ----
|
|
FROM node:22-alpine AS runner
|
|
WORKDIR /app
|
|
|
|
# Procedência (doutrina de packaging, invariante 2). O CI já injeta os labels
|
|
# OCI via docker/metadata-action; estes aqui são defesa em profundidade — valem
|
|
# para qualquer build, inclusive o local de docker-compose.build.yml, que não
|
|
# passa pelo metadata-action e sem isto sairia sem origem nenhuma.
|
|
LABEL org.opencontainers.image.source="https://github.com/melgarafael/DeskcommCRM" \
|
|
org.opencontainers.image.licenses="MIT" \
|
|
org.opencontainers.image.title="DeskcommCRM"
|
|
|
|
# A versão que /api/v1/health reporta (invariante 7). Precisa vir por ARG: a
|
|
# alternativa anterior era `process.env.npm_package_version`, que é `undefined`
|
|
# sob `CMD ["node","server.js"]` — só existe quando o processo nasce de um
|
|
# `npm`/`pnpm run`. Toda instalação do mundo reportava o fallback "0.1.0".
|
|
ARG APP_VERSION=dev
|
|
ENV NODE_ENV=production \
|
|
PORT=3000 \
|
|
HOSTNAME=0.0.0.0 \
|
|
NEXT_TELEMETRY_DISABLED=1 \
|
|
APP_VERSION=$APP_VERSION
|
|
# ffmpeg: a derivação de vídeo (Onda 3.1) roda no processo do app — o cron
|
|
# event-log-drain executa o media_derive handler, que chama `ffmpeg` via spawn
|
|
# pra extrair áudio+frames. Sem o binário, todo vídeo recebido falha a derivação.
|
|
RUN apk add --no-cache ffmpeg
|
|
# non-root
|
|
RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001
|
|
# O output standalone NÃO inclui public/ nem .next/static — copiar explicitamente,
|
|
# senão CSS/JS/assets retornam 404 (app "sem estilo").
|
|
COPY --from=build --chown=nextjs:nodejs /app/.next/standalone ./
|
|
COPY --from=build --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
COPY --from=build --chown=nextjs:nodejs /app/public ./public
|
|
USER nextjs
|
|
EXPOSE 3000
|
|
# server.js é o entrypoint gerado pelo output standalone.
|
|
CMD ["node", "server.js"]
|