96 lines
4.5 KiB
Docker
96 lines
4.5 KiB
Docker
# Imagem única para os dois processos do módulo: web (Next standalone) e worker.
|
|
# Rodar os dois do mesmo artefato garante que web e worker compartilhem
|
|
# exatamente o mesmo código e as mesmas dependências em homologação.
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Base
|
|
# ---------------------------------------------------------------------------
|
|
FROM node:24-bookworm-slim AS base
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
WORKDIR /app
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dependências de produção. Inclui `tsx`, que o worker e os scripts
|
|
# operacionais usam em runtime — por isso `tsx` está em `dependencies`.
|
|
# `--ignore-scripts` é seguro aqui: nenhuma dependência de produção depende de
|
|
# postinstall, e o binário do esbuild vem do pacote de plataforma.
|
|
# ---------------------------------------------------------------------------
|
|
FROM base AS deps-prod
|
|
COPY package.json package-lock.json ./
|
|
RUN npm ci --omit=dev --ignore-scripts && npm cache clean --force
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dependências completas e build
|
|
# ---------------------------------------------------------------------------
|
|
FROM base AS builder
|
|
COPY package.json package-lock.json ./
|
|
RUN npm ci --ignore-scripts
|
|
COPY tsconfig.json next.config.ts postcss.config.mjs eslint.config.mjs next-env.d.ts ./
|
|
COPY src ./src
|
|
COPY migrations ./migrations
|
|
COPY scripts ./scripts
|
|
# Release do Sentry: o SHA do commit entra no bundle do cliente e no servidor,
|
|
# para o stack trace do evento apontar o código certo. Não é segredo.
|
|
ARG SENTRY_RELEASE=""
|
|
ENV SENTRY_RELEASE=$SENTRY_RELEASE
|
|
ENV NEXT_PUBLIC_SENTRY_RELEASE=$SENTRY_RELEASE
|
|
# O build não recebe segredo: `collectConfigIssues` é ignorado na fase de build
|
|
# e a validação acontece no start do container.
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
RUN npm run build
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Runtime
|
|
# ---------------------------------------------------------------------------
|
|
FROM base AS runner
|
|
ARG SENTRY_RELEASE=""
|
|
ENV NODE_ENV=production
|
|
ENV PORT=3000
|
|
ENV HOSTNAME=0.0.0.0
|
|
ENV SENTRY_RELEASE=$SENTRY_RELEASE
|
|
ENV ADAPTER_WORKER_HEARTBEAT=/tmp/adaptador-worker-heartbeat
|
|
|
|
RUN groupadd --system --gid 1001 adaptador \
|
|
&& useradd --system --uid 1001 --gid adaptador adaptador
|
|
|
|
# 1) node_modules de produção primeiro. Traz `tsx` para o worker e os scripts.
|
|
COPY --from=deps-prod --chown=adaptador:adaptador /app/node_modules ./node_modules
|
|
|
|
# 2) Saída autocontida do Next por cima. Onde um pacote existe nas duas árvores,
|
|
# a versão traçada do build é a que vale para o servidor. Conferido: as duas
|
|
# árvores vêm do mesmo lockfile e não divergem em nenhuma versão; a árvore
|
|
# traçada tem 3 pacotes que não ficam no topo da de produção
|
|
# (@emnapi/runtime, isarray, supports-color), e o COPY mescla sem removê-los.
|
|
# O package.json do standalone NÃO é sobrescrito: é o que o server.js espera.
|
|
COPY --from=builder --chown=adaptador:adaptador /app/.next/standalone ./
|
|
COPY --from=builder --chown=adaptador:adaptador /app/.next/static ./.next/static
|
|
|
|
# 3) Arquivos lidos em runtime: migrations pelo migrate, src e scripts pelo
|
|
# worker, tsconfig pelos aliases `@/` do tsx.
|
|
COPY --chown=adaptador:adaptador migrations ./migrations
|
|
COPY --chown=adaptador:adaptador scripts ./scripts
|
|
COPY --chown=adaptador:adaptador src ./src
|
|
COPY --chown=adaptador:adaptador tsconfig.json ./
|
|
COPY --chown=adaptador:adaptador docker ./docker
|
|
|
|
# 4) Ponto de montagem do armazenamento local já com o dono certo. Um volume
|
|
# nomeado VAZIO herda dono e modo deste diretório na primeira montagem. Sem
|
|
# ele, o Docker cria /app/.data como root:root 755 e o uid 1001 não escreve
|
|
# (STORAGE_NOT_WRITABLE). Volumes que já existem não herdam nada: para eles
|
|
# há o serviço storage-init do compose.
|
|
RUN mkdir -p /app/.data/storage \
|
|
&& chown adaptador:adaptador /app/.data /app/.data/storage \
|
|
&& chmod 750 /app/.data /app/.data/storage
|
|
|
|
USER adaptador
|
|
EXPOSE 3000
|
|
|
|
# Liveness não toca dependência externa: distingue processo vivo de dependência
|
|
# fora do ar, que é o papel de /api/health/ready. O contêiner do worker troca
|
|
# este healthcheck por `worker-health` no compose.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
|
CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/api/health/live').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
|
|
ENTRYPOINT ["/app/docker/entrypoint.sh"]
|
|
CMD ["web"]
|