Dockerfile
Rama main · réplica de lectura del repositorio original.
# =============================================================================
# SocialiseIT Production Dockerfile (Optimized for Portainer)
# Layer-cached multi-stage build for fast rebuilds
# =============================================================================
# -----------------------------------------------------------------------------
# Stage 1: Runtime Base (Shared by build AND final images)
# FFmpeg installed ONCE here for video transcoding, reused everywhere
# -----------------------------------------------------------------------------
FROM node:20-slim AS runtime-base
RUN apt-get update && apt-get install -y --no-install-recommends \
openssl \
ffmpeg \
curl \
gosu \
&& rm -rf /var/lib/apt/lists/*
# -----------------------------------------------------------------------------
# Stage 2: Build Base (Extends runtime-base with build tools)
# -----------------------------------------------------------------------------
FROM runtime-base AS build-base
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
python3 \
&& rm -rf /var/lib/apt/lists/*
# -----------------------------------------------------------------------------
# Stage 3: Dependencies (Cached - changes only when package.json changes)
# -----------------------------------------------------------------------------
FROM build-base AS deps
WORKDIR /app
# Copy ONLY package files first (maximizes cache hits)
COPY app/package*.json ./
COPY app/.npmrc ./.npmrc
COPY app/tools/minimatch-compat ./tools/minimatch-compat
COPY app/prisma ./prisma
COPY app/prisma.config.ts ./prisma.config.ts
RUN npm ci && npm install --no-save esbuild
# Generate Prisma client before source copy (cached if schema unchanged)
ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy"
RUN npx prisma generate
# -----------------------------------------------------------------------------
# Stage 4: Source Build (Rebuilds when code changes, deps cached)
# -----------------------------------------------------------------------------
FROM deps AS source
# Now copy source code - this layer invalidates on code changes
# But deps layer above stays cached!
COPY app/ .
# -----------------------------------------------------------------------------
# Stage 5: Webapp Builder
# -----------------------------------------------------------------------------
FROM source AS webapp-builder
ENV NEXT_TELEMETRY_DISABLED=1
# Required for Next.js page data collection (Prisma client init check)
ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy"
# Tells first-run-check.ts to skip DB queries during build
ENV NEXT_PHASE="phase-production-build"
# Turbopack 16.2 can emit loadable manifests that reference discarded chunks
# when dashboard route modules are lazy-loaded. The build script uses Webpack
# for production until that upstream issue is resolved.
RUN rm -rf .next && npm run build && npm run verify:chunks
# -----------------------------------------------------------------------------
# Stage 5.5: Prisma CLI Dependencies (runs IN PARALLEL with webapp-builder)
# Docker BuildKit builds independent stages concurrently - no wall-clock penalty.
# prisma CLI has 30+ transitive deps that must be properly resolved by npm.
# -----------------------------------------------------------------------------
FROM node:20-slim AS prisma-cli-deps
WORKDIR /tmp/prisma-cli
# Read the exact CLI version without installing the application's unlocked tree.
COPY app/package-lock.json ./app-package-lock.json
RUN PRISMA_VERSION="$(node -p "require('./app-package-lock.json').packages['node_modules/prisma'].version")" \
&& npm install --ignore-scripts --no-save "prisma@${PRISMA_VERSION}" \
&& test ! -d node_modules/next
# -----------------------------------------------------------------------------
# Stage 6: Webapp Runner (Minimal - extends runtime-base, NO build tools)
# -----------------------------------------------------------------------------
FROM runtime-base AS webapp
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
# Copy built artifacts
COPY --from=webapp-builder /app/public ./public
COPY --from=webapp-builder /app/.next/standalone ./
COPY --from=webapp-builder /app/.next/static ./.next/static
# Prisma runtime: CLI deps from parallel stage + build-generated client
COPY --from=webapp-builder /app/prisma ./prisma
COPY --from=webapp-builder /app/prisma.config.ts ./prisma.config.ts
COPY --from=webapp-builder /app/src/generated/prisma ./src/generated/prisma
# Never overlay CLI dependencies onto Next's standalone dependency tree:
# doing so can replace Next with a version incompatible with the built output.
COPY --from=prisma-cli-deps /tmp/prisma-cli/node_modules /opt/prisma-cli/node_modules
# Overlay the build-generated @prisma/client (schema-specific, from prisma generate)
COPY --from=webapp-builder /app/node_modules/@prisma/client ./node_modules/@prisma/client
COPY --from=webapp-builder /app/node_modules/valibot ./node_modules/valibot
COPY app/docker-entrypoint.sh ./docker-entrypoint.sh
RUN mkdir -p ./public/uploads ./public/uploads/transcoded && \
chmod +x ./docker-entrypoint.sh && \
chown -R nextjs:nodejs /app
# Why: USER is NOT set here. The entrypoint runs as root to fix volume
# permissions (Docker named volumes may be root-owned), then drops to
# nextjs via gosu. See docker-entrypoint.sh.
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:3000/api/health || exit 1
ENTRYPOINT ["./docker-entrypoint.sh"]
# Stage 8: Worker Builder (Compiles TS to JS - eliminates tsx transpilation at startup)
# -----------------------------------------------------------------------------
FROM source AS worker-builder
# Bundle worker entry point: all local TS is transpiled + path aliases resolved;
# node_modules packages stay external so native binaries (Prisma WASM etc.) load
# from their original paths at runtime. esbuild preserves __dirname per-module
# so relative binary/WASM loads in the generated Prisma client still resolve correctly.
# Why: Prisma's generated client.ts uses import.meta.url to locate its engine
# binary via fileURLToPath(import.meta.url). esbuild bundles it into a CommonJS
# output where import.meta.url is undefined, causing a crash at startup.
# Defining import.meta.url as the known container path gives Prisma a real URL
# so it can compute __dirname = /app/src/generated/prisma correctly.
RUN node_modules/.bin/esbuild src/workers/index.ts \
--bundle \
--platform=node \
--packages=external \
--tsconfig=tsconfig.json \
'--define:import.meta.url="file:///app/src/generated/prisma/client.ts"' \
--outfile=dist/worker.js \
--metafile=dist/worker.meta.json \
&& node scripts/verify-worker-imports.mjs dist/worker.meta.json
# -----------------------------------------------------------------------------
# Stage 9: Worker Runner (Uses compiled JS, no tsx transpilation on startup)
# -----------------------------------------------------------------------------
FROM source AS worker
ENV NODE_ENV=production
# Copy pre-compiled worker bundle
COPY --from=worker-builder /app/dist/worker.js ./dist/worker.js
COPY app/docker-entrypoint.worker.sh ./docker-entrypoint.worker.sh
RUN chmod +x ./docker-entrypoint.worker.sh
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD pgrep -f "node" || exit 1
ENTRYPOINT ["./docker-entrypoint.worker.sh"]