Repository navigation
Expand file tree
/
Copy pathDockerfile
More file actions
138 lines (131 loc) · 6.76 KB
/
Copy pathDockerfile
File metadata and controls
138 lines (131 loc) · 6.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# 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"]