Dockerfile
Rama main · réplica de lectura del repositorio original.
# syntax=docker/dockerfile:1
# Shared build for React Router SSR apps. Build: docker build --build-arg APP=erp -t carbon/erp .
ARG APP
# SOURCEMAPS=1 keeps node_modules sourcemaps for a debuggable image. Nothing
# reads them at runtime (no --enable-source-maps), so they go by default.
ARG SOURCEMAPS=0
# slim, not the full node image — every native dep ships prebuilt, nothing needs the toolchain.
FROM node:24.22.1-slim AS deps
WORKDIR /repo
RUN corepack enable
# Store on a cache mount, so a source-only commit relinks instead of refetching.
ENV npm_config_store_dir=/pnpm/store
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc turbo.json lingui.config.js ./
# Only the apps this image can build — the rest would bust this layer for nothing.
COPY apps/erp ./apps/erp
COPY apps/mes ./apps/mes
COPY packages ./packages
COPY patches ./patches
# Needed by the postinstall and the //#generate:mcp turbo task.
COPY scripts ./scripts
# @carbon/content (glossary, the agent's doc corpus) lives with the docs it serves.
COPY docs/content ./docs/content
# Set before the install: its postinstall runs //#generate:mcp, which needs more
# than node's default ~2 GB heap. Inherited by `build`.
ARG NODE_OPTIONS="--max-old-space-size=8024"
ENV NODE_OPTIONS=${NODE_OPTIONS}
# CI=1 as on any CI install: the postinstall leaves the MCP manifest to the task
# that reads it. `build` makes it through turbo for erp; nothing else here does.
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
CI=1 pnpm install --frozen-lockfile
FROM deps AS build
ARG APP
RUN --mount=type=cache,id=turbo,target=/repo/.turbo,sharing=locked \
pnpm run build:${APP}
# Build scratch `runner` must not inherit: .vite is the dep-optimizer cache,
# .ignored_<name> is pnpm's per-importer copy of a side-effects-cached package.
RUN rm -rf apps/${APP}/node_modules/.vite apps/${APP}/node_modules/.ignored_*
# --- Bootstrap image (DB migrations + first-boot seed) ---------------------
# Runs two commands, both in packages/database: `supabase migration up` and
# `tsx src/seed.ts`. It installs that one package and nothing else, so it does
# not build on `deps`. It keeps the supabase CLI and tsx/esbuild that `runner`
# strips for its CVE posture, so it is never exposed and is scanned
# report-only. Kept BEFORE `runner` so `runner` stays the default build stage.
FROM node:24.22.1-slim AS bootstrap-deps
WORKDIR /repo
RUN corepack enable
ENV npm_config_store_dir=/pnpm/store
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY patches ./patches
# @carbon/config is the package's one workspace dependency.
COPY packages/config ./packages/config
COPY packages/database ./packages/database
# --ignore-scripts keeps the root postinstall out (it needs turbo and the whole
# repo); the rebuild then runs the dependencies' own install scripts, which is
# where the supabase CLI binary is downloaded.
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
pnpm install --frozen-lockfile --filter @carbon/database --ignore-scripts \
&& pnpm --filter @carbon/database rebuild
# A workspace install always brings the root project's tooling and the rest of
# this package's dependencies. Neither command loads these, so the heavy ones
# go; a package missing from this list only costs size.
RUN find node_modules/.pnpm -maxdepth 1 -type d \( \
-name 'sst@*' -o -name 'sst-linux-*' -o -name 'sst-darwin-*' -o -name 'sst-win32-*' -o \
-name '@biomejs+*' -o \
-name 'turbo@*' -o -name '@turbo+*' -o \
-name 'typescript@*' -o \
-name 'ts-morph@*' -o -name '@ts-morph+*' -o \
-name 'vite@*' -o -name 'rolldown@*' -o -name '@rolldown+*' -o \
-name 'vitest@*' -o -name '@vitest+*' -o \
-name 'npm@*' -o \
-name 'pdfjs-dist@*' -o -name '@napi-rs+canvas*' -o \
-name 'libpg-query@*' \
\) -prune -exec rm -rf {} + ; \
find node_modules -type f \( -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' \
-o -name '*.md' -o -name '*.map' \) -delete 2>/dev/null || true
FROM node:24.22.1-slim AS bootstrap
# slim ships no CA certs, and the supabase CLI is a Go binary that verifies TLS
# against the system store — migrations default to sslmode=require.
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN corepack enable
# Pre-seeded corepack cache, so `pnpm exec` never dials npmjs from the migrate Job.
COPY --from=bootstrap-deps /root/.cache/node/corepack /root/.cache/node/corepack
COPY --from=bootstrap-deps /repo /repo
WORKDIR /repo/packages/database
CMD ["bash"]
# The image's former name. BYOC builds it by this target; remove once it
# builds `bootstrap`.
FROM bootstrap AS ops
# --- Runtime dependency tree ----------------------------------------------
# The app's production dependencies, installed on their own: what the built
# server loads from node_modules. The bundle inlines everything else, so that
# is @carbon/serve, which `pnpm start` runs from source, and whatever the
# bundle leaves external (erp's AI SDK). @carbon/database's too: the chart's
# wait-for-migrations init container runs this image in packages/database
# and loads pg from there. Every package lists in
# `dependencies` only what its runtime code imports, so no build tool, CLI or
# test runner is installed here. Its own stage, so the files it deletes leave
# no layer behind in `runner`.
FROM deps AS prod-deps
ARG APP
ARG SOURCEMAPS
RUN rm -rf node_modules apps/*/node_modules packages/*/node_modules docs/content/node_modules
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
pnpm install --prod --frozen-lockfile --prefer-offline --ignore-scripts \
--filter-prod "${APP}..." --filter-prod @carbon/database
COPY --from=build /repo/apps/${APP}/build ./apps/${APP}/build
RUN find node_modules -type f \( -name '*.d.ts' -o -name '*.d.mts' \
-o -name '*.d.cts' -o -name '*.md' \) -delete 2>/dev/null || true ; \
if [ "${SOURCEMAPS}" != "1" ]; then \
find node_modules -type f -name '*.map' -delete 2>/dev/null || true ; \
fi
FROM node:24.22.1-slim AS runner
ARG APP
WORKDIR /repo
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
RUN corepack enable
ENV NODE_ENV=production
ENV PORT=3000
# Date derivation assumes UTC until company/location timezones are threaded everywhere
ENV TZ=UTC
COPY --from=deps /repo/package.json /repo/pnpm-lock.yaml /repo/pnpm-workspace.yaml /repo/.npmrc ./
COPY --from=prod-deps /repo/node_modules ./node_modules
COPY --from=prod-deps /repo/packages ./packages
COPY --from=prod-deps /repo/apps/${APP} ./apps/${APP}
# The base image's npm is unused (corepack/pnpm only) and vendors the last Trivy CRITICALs.
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
EXPOSE 3000
WORKDIR /repo/apps/${APP}
CMD ["pnpm","run","start"]