# ───────────────────────────────────────────────────────────────── # Workflow PocketBase Frontend — Dockerfile (Coolify-ready) # # Multi-stage build for a small production image: # - deps: install everything # - builder: compile the Next.js app (writes standalone output) # - runner: copy the standalone build + public assets + .next/static # onto the alpine image # # Image runs as non-root user `nextjs` on port 3000. # # Build context: this folder (where package.json lives). # Local: docker build -t workflow-frontend . # docker run --rm -p 3000:3000 \ # -e PB_URL=https://pb.agent.apphub.co.za \ # -e PB_TOKEN=... \ # workflow-frontend # # Coolify: set "Dockerfile" as the build pack, port 3000, and configure # Build Arguments (NEXT_PUBLIC_PB_URL, NEXT_PUBLIC_BASE_URL, # NEXT_PUBLIC_APP_URL) + Runtime Environment (PB_URL, PB_TOKEN, # TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, ONBOARDING_TOKEN_PEPPER). # See docker-compose.yml for the same contract in one place. # # NOTE — routes that shell out to local Windows tooling do NOT work in # this Linux image: the CIPC/BizPortal routes drive agent-browser and the # BizPortal sidecar, and PDF rendering expects a Windows Chrome or the # PDFD_URL sidecar. They stay on the PC; host them separately. # ───────────────────────────────────────────────────────────────── # ─── 1. Deps ───────────────────────────────────────────────────── FROM node:20-alpine AS deps WORKDIR /app COPY package.json package-lock.json* ./ RUN \ if [ -f package-lock.json ]; then npm ci; \ else npm install; \ fi # ─── 2. Builder ────────────────────────────────────────────────── FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . # Required at build-time so Next.js bakes them into the client bundle. # Next.js INLINES every NEXT_PUBLIC_* at build time — if they are missing # here, the browser bundle ships with an empty PocketBase URL and every # client-side PB call fails. Set them as Build Arguments in Coolify. # # NEXT_PUBLIC_PB_URL — read by components/CipcAnnualReturns.tsx and # components/DocumentsPanel.tsx # NEXT_PUBLIC_BASE_URL — read by app/onboarding/new/page.tsx (link the # client sends in the onboarding e-mail) # NEXT_PUBLIC_APP_URL — absolute app URL used in appointment e-mails # # Server-only secrets (PB_URL, PB_TOKEN, TELEGRAM_*, ONBOARDING_TOKEN_PEPPER) # are NOT needed here — they are read at runtime, never bundled. ARG NEXT_PUBLIC_PB_URL ARG NEXT_PUBLIC_BASE_URL ARG NEXT_PUBLIC_APP_URL ENV NEXT_PUBLIC_PB_URL=${NEXT_PUBLIC_PB_URL} \ NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL} \ NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL} \ NEXT_TELEMETRY_DISABLED=1 RUN npm run build # ─── 3. Runner ─────────────────────────────────────────────────── FROM node:20-alpine AS runner WORKDIR /app # wget is needed by the HEALTHCHECK below (not in node:20-alpine # by default — busybox-extra ships it). RUN apk add --no-cache wget ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV PORT=3000 ENV HOSTNAME=0.0.0.0 # Non-root user RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs # Copy the standalone build (Next.js output tracing) + static assets. # `public/` is optional (PWA manifest, favicons, robots.txt). Next # inlines any source `public/` into the standalone build, so we only # need to copy it if it actually exists. Projects without a public/ # dir (e.g. this one pre-PWA) no-op cleanly. COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static RUN if [ -d ./public ] && [ -n "$(ls -A ./public 2>/dev/null)" ]; then \ echo "public/ assets found in standalone build, keeping"; \ else \ rm -rf ./public 2>/dev/null || true; \ fi USER nextjs EXPOSE 3000 # Health check — Coolify / Docker can probe /api/pb/records/list and # 401 / 500 is fine here (it means the app booted; PB may be # unreachable but the proxy is running). A connection refused means # the Next.js server hasn't bound yet. HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ CMD wget -qO- http://127.0.0.1:3000/api/pb/records/list?collection=ysterbaard_directors\&perPage=1 >/dev/null || exit 1 CMD ["node", "server.js"]