# ============================================================================== # EpicNext-CMS — Docker image (Node 26.8.1, multi-package-manager, Next.js standalone) # ============================================================================== # Supports pnpm (default), npm, and yarn. The build stage detects which package # manager lockfile is present and uses it automatically. # ============================================================================== # --- Builder stage --- FROM node:26.8.1-bookworm-slim AS builder # git is needed by next.config.ts (git rev-parse for deploymentId) and # ca-certificates by package registries. Build runs on host network (see # compose: iptables is disabled), so apt has registry access here. RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \ && rm -rf /var/lib/apt/lists/* # Install all three package managers so the build can pick whichever lockfile exists. RUN npm install -g pnpm@11.25.0 yarn WORKDIR /app # First copy only the manifests so dependency layers are cached when using pnpm. # For npm/yarn the full context is copied below before install. COPY package.json pnpm-workspace.yaml .npmrc ./ # Copy the rest of the source (brings in whichever lockfile your project uses). COPY . . # --- Detect package manager & install dependencies --- # Priority: pnpm > yarn > npm # Build arg lets the user force a manager; otherwise it is auto-detected. ARG PACKAGE_MANAGER= RUN if [ "$PACKAGE_MANAGER" = "pnpm" ] || { [ -z "$PACKAGE_MANAGER" ] && [ -f pnpm-lock.yaml ]; }; then \ echo ">> Using pnpm" && \ pnpm install --frozen-lockfile --ignore-scripts; \ elif [ "$PACKAGE_MANAGER" = "yarn" ] || { [ -z "$PACKAGE_MANAGER" ] && [ -f yarn.lock ]; }; then \ echo ">> Using yarn" && \ yarn install --frozen-lockfile --ignore-scripts; \ elif [ "$PACKAGE_MANAGER" = "npm" ] || { [ -z "$PACKAGE_MANAGER" ] && [ -f package-lock.json ]; }; then \ echo ">> Using npm" && \ npm ci --ignore-scripts; \ else \ echo "!! No lockfile found — falling back to npm install" && \ npm install --ignore-scripts; \ fi # Build the production bundle. # The .env file is loaded ONLY inside this RUN layer (not persisted as ENV, so no # secrets end up in the image) — Next.js needs NEXT_PUBLIC_* + validated build-time # values (HOTEL_NAME, DATABASE_URL, AUTH_SECRET, ...) at build time. ENV NODE_ENV=production RUN if [ -f .env ]; then set -a && . ./.env && set +a; fi && \ if [ "$PACKAGE_MANAGER" = "yarn" ] || { [ -z "$PACKAGE_MANAGER" ] && [ -f yarn.lock ]; }; then \ yarn build; \ elif [ "$PACKAGE_MANAGER" = "npm" ] || { [ -z "$PACKAGE_MANAGER" ] && [ -f package-lock.json ]; }; then \ npm run build; \ else \ pnpm build; \ fi # Prune dev dependencies for the runtime image. RUN if [ "$PACKAGE_MANAGER" = "yarn" ] || { [ -z "$PACKAGE_MANAGER" ] && [ -f yarn.lock ]; }; then \ yarn install --production --ignore-scripts && rm -rf node_modules/.cache; \ elif [ "$PACKAGE_MANAGER" = "npm" ] || { [ -z "$PACKAGE_MANAGER" ] && [ -f package-lock.json ]; }; then \ npm prune --production; \ else \ pnpm prune --prod; \ fi # --- Runtime stage --- FROM node:26.8.1-bookworm-slim AS runner # Catalog Studio publishes to self-hosted Git repositories over HTTPS. RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates && rm -rf /var/lib/apt/lists/* # The CMS writes to bind-mounted host directories (/var/www/Gamedata is owned by # the host's www-data user, UID/GID 33). The node base image already ships a # www-data user with UID/GID 33, which matches that ownership — so we run as # www-data and can write to the shared gamedata directory. If your host owner # differs, override via --build-arg RUN_USER (e.g. --build-arg RUN_USER=1000). ARG RUN_USER=www-data ENV NODE_ENV=production ENV PORT=3002 ENV HOSTNAME=0.0.0.0 EXPOSE 3002 WORKDIR /app # Storage directory for runtime uploaded media (persistent volume). # Also create the hardcoded gamedata mount point (/var/www/Gamedata is # bind-mounted at runtime so the CMS can read + write imported assets there). RUN mkdir -p /app/storage \ /app/public/nitro-assets \ /app/public/swf \ /var/www/Gamedata \ && chown -R ${RUN_USER} /app /var/www/Gamedata # Copy standalone Next.js output (includes a minimal node_modules). COPY --from=builder --chown=${RUN_USER} /app/.next/standalone ./ # Copy static assets (public files served directly). COPY --from=builder --chown=${RUN_USER} /app/public ./public # Copy the server-side static build output. COPY --from=builder --chown=${RUN_USER} /app/.next/static ./.next/static # Client assets + runtime uploads live outside the image (mounted volumes). VOLUME ["/app/public/nitro-assets", "/app/public/swf", "/app/storage"] USER ${RUN_USER} CMD ["node", "server.js"]