Files
family-planner/Dockerfile
jason 155e7849e1 Fix runtime stage: install prod deps instead of copying node_modules
pnpm uses a symlink-based virtual store (.pnpm/) that breaks when
copied between Docker stages — Node can't resolve modules from the
copied tree, causing 'Cannot find module express' at startup.

Replace the broken COPY with a proper pnpm install --prod in the
runtime stage. Layer caching still applies: manifests + lockfile
are copied before source so the install layer is only rebuilt when
deps change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 22:12:38 -05:00

95 lines
4.0 KiB
Docker

# ─────────────────────────────────────────────────────────────────────────────
# Stage 1 — Build client
# ─────────────────────────────────────────────────────────────────────────────
FROM node:22-alpine AS client-builder
WORKDIR /build
# Install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
# Copy workspace manifests first for better layer caching
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml* ./
COPY apps/client/package.json ./apps/client/
COPY apps/server/package.json ./apps/server/
COPY tsconfig.base.json ./
# Install all workspace deps
RUN pnpm install --frozen-lockfile
# Copy client source and build
COPY apps/client ./apps/client/
RUN pnpm --filter client build
# ─────────────────────────────────────────────────────────────────────────────
# Stage 2 — Build server
# ─────────────────────────────────────────────────────────────────────────────
FROM node:22-alpine AS server-builder
WORKDIR /build
RUN corepack enable && corepack prepare pnpm@latest --activate
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml* ./
COPY apps/server/package.json ./apps/server/
COPY apps/client/package.json ./apps/client/
COPY tsconfig.base.json ./
# Install only server production deps + rebuild native modules for target arch
RUN pnpm install --frozen-lockfile
COPY apps/server ./apps/server/
RUN pnpm --filter server build
# ─────────────────────────────────────────────────────────────────────────────
# Stage 3 — Production runtime
# ─────────────────────────────────────────────────────────────────────────────
FROM node:22-alpine AS runtime
# Install tini for proper PID 1 signal handling
RUN apk add --no-cache tini su-exec
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
# Copy workspace manifests and lockfile — pnpm needs these to install correctly
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml* .npmrc ./
COPY apps/server/package.json ./apps/server/
COPY apps/client/package.json ./apps/client/
# Install production dependencies only — fresh install avoids broken symlinks
# from COPY-ing pnpm's virtual store between stages
RUN pnpm install --prod --frozen-lockfile
# Copy compiled server output (includes dist/db/migrations/*.js compiled by tsc)
COPY --from=server-builder /build/apps/server/dist ./apps/server/dist
# Copy built client into the path the server expects
COPY --from=client-builder /build/apps/client/dist ./apps/client/dist
# ── Runtime configuration ─────────────────────────────────────────────────
ENV NODE_ENV=production \
PORT=3001 \
DATA_DIR=/data \
PHOTOS_DIR=/photos \
PUID=99 \
PGID=100 \
TZ=UTC \
NODE_NO_WARNINGS=1
# /data — persistent: SQLite database
# /photos — bind-mount: read-only user photo library
VOLUME ["/data"]
EXPOSE 3001
# Entrypoint: fix ownership then drop to PUID/PGID
COPY docker-entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"]
CMD ["node", "apps/server/dist/index.js"]