Repository navigation
Expand file tree
/
Copy pathDockerfile
More file actions
56 lines (53 loc) · 2.9 KB
/
Copy pathDockerfile
File metadata and controls
56 lines (53 loc) · 2.9 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
# syntax=docker/dockerfile:1@sha256:ecfaec9ed6d810b56388c508f4121597bfbba70d41a6dfeee4d8cad5f295fc32
FROM node:24-slim@sha256:55fb5f5a527ea2c5c4a95f963e48ef90aa38c7b346054fdc8ee9957a2b55d205 AS deps
WORKDIR /app
RUN corepack enable pnpm
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
# pnpm fetch populates the store from the lockfile alone, so this layer only
# invalidates when dependencies change, not on every source edit.
RUN pnpm fetch
FROM deps AS build
COPY . .
RUN pnpm install --frozen-lockfile --offline
# Optional upload of the dashboard's browser source maps to the error
# tracker (SELF_HOSTING.md, "Error tracking"). Without the secret the build
# is unchanged. The secret never enters the cache key; these args do, so a
# build that sets them is not served a layer built without the upload.
ARG SENTRY_URL
ARG SENTRY_ORG
ARG SENTRY_PROJECT
RUN --mount=type=secret,id=SENTRY_AUTH_TOKEN,env=SENTRY_AUTH_TOKEN \
pnpm --filter @millionsend/web build
RUN pnpm --filter @millionsend/docs build
# The webpack cache is build-only (hundreds of MB), and the runtime never
# needs dev tooling (drizzle-kit, vitest, biome, typescript): a fresh
# prod-only install from the same store leaves none of it behind. (`pnpm
# prune --prod` is not workspace-aware — it empties the packages' own
# node_modules — and a re-link over the existing tree keeps the orphans.)
RUN rm -rf apps/*/.next/cache node_modules apps/*/node_modules packages/*/node_modules \
&& CI=true pnpm install --prod --frozen-lockfile --offline
# The runtime keeps the full workspace (source + node_modules): api and worker
# run TypeScript directly via tsx (which resolves NodeNext ".js" specifiers to
# .ts sources), and workspace packages export TS source, so there is no pruned
# "dist" to copy. The image is larger than a bundled build; that is the
# accepted tradeoff for zero build tooling in the backends.
FROM node:24-slim@sha256:55fb5f5a527ea2c5c4a95f963e48ef90aa38c7b346054fdc8ee9957a2b55d205 AS runtime
WORKDIR /app
ARG GIT_SHA=unknown
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV MILLIONSEND_REVISION=$GIT_SHA
RUN groupadd --system millionsend && useradd --system --gid millionsend millionsend
# Root-owned tree: the process that runs this source cannot rewrite it. Only
# Next's runtime caches (image optimizer, fetch cache) and the docs' MDX
# index, which fumadocs regenerates on every start, need to be writable.
# Mode 1777 rather than ownership: a tmpfs mounted over these paths (the
# compose files do, for a read-only rootfs) inherits the mode, not the owner.
COPY --from=build /app /app
RUN install -d -m 1777 apps/web/.next/cache apps/docs/.next/cache \
&& chown -R millionsend:millionsend apps/docs/.source && chmod 1777 apps/docs/.source
USER millionsend
EXPOSE 3000 3001 3002
# ENTRYPOINT (not CMD) so `docker compose run millionsend setup` reaches
# start.mjs as argv instead of replacing the command.
ENTRYPOINT ["node", "scripts/start.mjs"]