Uploads go to a named volume at /app/media. The directory is created in the image before the mount and covered by the existing chown, because Docker seeds a fresh named volume from the image path — a missing or root-owned directory there fails every upload with EACCES at runtime, long after the build passed. PAYLOAD_SECRET uses the same :? form as AIRFLOW_ADMIN_PASSWORD: refuse to start rather than boot with an empty secret and accept forged sessions. Staging's must differ from production's, which the header comment now says explicitly. Portainer prefixes volume names per stack, so payload_media isolates itself. prodMigrations is not wired yet — generating the initial migration needs a reachable Postgres. Follows in its own commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017YmbBhr8s7GusjDE12hrZM
78 lines
2.1 KiB
Docker
78 lines
2.1 KiB
Docker
# Multi-stage build for Next.js application
|
|
|
|
# Stage 1: Dependencies
|
|
FROM node:24-alpine AS deps
|
|
WORKDIR /app
|
|
|
|
# Copy package files
|
|
COPY package.json package-lock.json* ./
|
|
|
|
# Install all dependencies (including devDependencies needed for build)
|
|
RUN npm ci
|
|
|
|
# Stage 2: Builder
|
|
FROM node:24-alpine AS builder
|
|
WORKDIR /app
|
|
|
|
# Copy dependencies from deps stage
|
|
COPY --from=deps /app/node_modules ./node_modules
|
|
COPY . .
|
|
|
|
# Set environment variables for build
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
ENV NODE_ENV=production
|
|
|
|
# Default backend URL for any server-side fetch during `next build`. The
|
|
# runtime /api proxy reads FASTAPI_URL per request (see app/api/[...path]),
|
|
# so the deployed container's env is what actually routes traffic.
|
|
ARG FASTAPI_URL=http://backend:80/api
|
|
ENV FASTAPI_URL=${FASTAPI_URL}
|
|
|
|
# Build application
|
|
RUN npm run build
|
|
|
|
# Stage 3: Runner
|
|
FROM node:24-alpine AS runner
|
|
WORKDIR /app
|
|
|
|
ENV NODE_ENV=production
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
|
|
# Create non-root user
|
|
RUN addgroup --system --gid 1001 nodejs
|
|
RUN adduser --system --uid 1001 nextjs
|
|
|
|
# Copy necessary files
|
|
COPY --from=builder /app/public ./public
|
|
COPY --from=builder /app/.next/standalone ./
|
|
COPY --from=builder /app/.next/static ./.next/static
|
|
|
|
# Fonts for the generated share card. File tracing already places these in
|
|
# .next/standalone/assets, so the COPY above carries them — this makes the
|
|
# dependency explicit rather than implicit in the tracer's behaviour, because
|
|
# a miss here is a silent 500 on /opengraph-image, not a build failure.
|
|
COPY --from=builder /app/assets ./assets
|
|
|
|
# Payload writes uploads here, and the compose file mounts a named volume over
|
|
# it. The directory must exist and be owned by the runtime user BEFORE the
|
|
# mount: Docker seeds a fresh named volume from the image path, so a missing or
|
|
# root-owned directory here makes every upload fail with EACCES at runtime,
|
|
# long after the build passed. The chown below covers it.
|
|
RUN mkdir -p /app/media
|
|
|
|
# Set correct permissions
|
|
RUN chown -R nextjs:nodejs /app
|
|
|
|
# Switch to non-root user
|
|
USER nextjs
|
|
|
|
# Expose port
|
|
EXPOSE 3000
|
|
|
|
# Set environment variables
|
|
ENV PORT=3000
|
|
ENV HOSTNAME="0.0.0.0"
|
|
|
|
# Start application
|
|
CMD ["node", "server.js"]
|