Dockerfile
Rama main · réplica de lectura del repositorio original.
# --- Builder stage ---------------------------------------------------------
# Runs natively on the CI host for every target platform instead of under
# QEMU: the output is plain JS, and none of the server's production
# dependencies ships native code. Revisit if one ever does.
FROM --platform=$BUILDPLATFORM node:24-alpine AS builder
WORKDIR /app
# Copy workspace manifests first for cached deps install. The lock is
# committed and load-bearing (it pins the musl Rolldown binding this Alpine
# build needs), so copy it unconditionally and install with `npm ci`, which
# follows it exactly and fails loudly on lock/manifest drift.
COPY package.json package-lock.json ./
COPY shared/package.json shared/
COPY server/package.json server/
COPY client/package.json client/
RUN npm ci
# Copy sources and build.
COPY tsconfig.base.json ./
COPY shared/ ./shared/
COPY server/ ./server/
COPY client/ ./client/
RUN npm run build
# The client's runtime packages (CodeMirror, Shiki, Yjs, fonts...) are fully
# bundled into server/dist/public by the build, and the toolchain is only
# needed in this stage; reinstall just the server workspace's production
# dependencies so the runtime copy carries nothing else.
RUN rm -rf node_modules shared/node_modules server/node_modules client/node_modules \
&& npm ci --omit=dev -w server
# --- Runtime stage ---------------------------------------------------------
FROM node:24-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
# Copy node_modules + workspace manifests + built artifacts from the builder.
COPY --from=builder /app/package.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/shared/package.json shared/
COPY --from=builder /app/shared/dist shared/dist
COPY --from=builder /app/server/package.json server/
COPY --from=builder /app/server/dist server/dist
# Drop root: the official node:alpine image ships a `node` user (uid 1000).
# Running unprivileged limits the blast radius if a future vulnerability in
# a dependency lets an attacker execute code in the container.
USER node
EXPOSE 3000
# busybox wget ships with Alpine; /healthz is served before the rate limiter
# so the probe never eats the per-IP budget.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
CMD wget -qO- "http://127.0.0.1:${PORT:-3000}/healthz" || exit 1
CMD ["node", "server/dist/index.js"]