# syntax=docker/dockerfile:1 # ───────────────────────────────────────────────────────────────────────────── # Property Management Network — production image for DigitalOcean App Platform (DOCR) # Multi-stage build producing a slim Next.js standalone server. # ───────────────────────────────────────────────────────────────────────────── FROM node:22-alpine AS base # libc6-compat keeps some native/optional deps happy on Alpine. RUN apk add --no-cache libc6-compat WORKDIR /app # ── Install dependencies (cached on lockfile) ──────────────────────────────── FROM base AS deps COPY package.json package-lock.json ./ RUN npm ci # ── Build the app ──────────────────────────────────────────────────────────── FROM base AS builder ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 # NEXT_PUBLIC_* values are inlined into the client bundle at build time, so they # must be present here. Pass them as --build-arg when building the image. ARG NEXT_PUBLIC_APP_URL ARG NEXT_PUBLIC_APP_NAME="Property Management Network" ARG NEXT_PUBLIC_TURNSTILE_SITE_KEY ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL ENV NEXT_PUBLIC_APP_NAME=$NEXT_PUBLIC_APP_NAME ENV NEXT_PUBLIC_TURNSTILE_SITE_KEY=$NEXT_PUBLIC_TURNSTILE_SITE_KEY COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build # ── Runtime image ──────────────────────────────────────────────────────────── FROM base AS runner ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV PORT=3000 ENV HOSTNAME=0.0.0.0 # Uploaded documents/photos live here — mount a persistent volume on this path. ENV STORAGE_DIR=/app/storage RUN addgroup -g 1001 -S nodejs && adduser -u 1001 -S nextjs -G nodejs # Next.js standalone output: server.js + the minimal node_modules it traced. COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static # Migration runner: the SQL files, the script, and the full drizzle-orm package # (standalone tracing omits the migrator submodule the app never imports). COPY --from=builder /app/lib/db/migrations ./lib/db/migrations COPY --from=builder /app/node_modules/drizzle-orm ./node_modules/drizzle-orm COPY scripts/migrate.mjs ./scripts/migrate.mjs COPY docker-entrypoint.sh ./docker-entrypoint.sh RUN chmod +x ./docker-entrypoint.sh # Create the storage mount point owned by the runtime user. RUN mkdir -p /app/storage && chown -R nextjs:nodejs /app/storage USER nextjs EXPOSE 3000 # Liveness probe (also used by App Platform health checks). Uses Node's global fetch — no curl/wget needed. HEALTHCHECK --interval=30s --timeout=5s --start-period=25s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" ENTRYPOINT ["./docker-entrypoint.sh"]