# syntax=docker/dockerfile:1 # # Container image for Dokploy (Docker + Traefik). Single Node process that serves the built # React SPA and the Fastify API on one port. Documents live in DigitalOcean Spaces and logs go # to stdout, so the container is stateless — no volumes required. # ─── Builder ──────────────────────────────────────────────────────────────────── FROM node:20-bookworm-slim AS builder WORKDIR /app # Toolchain for native modules (argon2) in case no prebuilt binary is available for this platform. RUN apt-get update \ && apt-get install -y --no-install-recommends python3 make g++ \ && rm -rf /var/lib/apt/lists/* # Install dependencies first for better layer caching — copy every workspace manifest, then npm ci. COPY package.json package-lock.json ./ COPY apps/api/package.json ./apps/api/ COPY apps/web/package.json ./apps/web/ COPY packages/db/package.json ./packages/db/ RUN npm ci # Copy the rest of the source. COPY . . # Public config baked into the Vite bundle. Vite inlines VITE_* AT BUILD TIME, so these must be # provided as build args (Dokploy → Build → Build-time variables), NOT as runtime env vars. # Both are optional: an empty value simply disables that feature in the browser bundle. ARG VITE_TURNSTILE_SITE_KEY="" ARG VITE_SENTRY_DSN="" ENV VITE_TURNSTILE_SITE_KEY=${VITE_TURNSTILE_SITE_KEY} \ VITE_SENTRY_DSN=${VITE_SENTRY_DSN} # Build the SPA → apps/web/dist. The API runs from TypeScript source via the tsx loader, so there # is no separate API build step. RUN npm run build # ─── Runtime ──────────────────────────────────────────────────────────────────── FROM node:20-bookworm-slim AS runtime WORKDIR /app ENV NODE_ENV=production \ PORT=8080 # ca-certificates for outbound TLS (Postgres, Spaces, Stripe, SMTP2GO, Anthropic). RUN apt-get update \ && apt-get install -y --no-install-recommends ca-certificates \ && rm -rf /var/lib/apt/lists/* \ && useradd -m -u 1001 app # Bring over the fully-installed, already-built app (node_modules incl. the compiled argon2 binary # and workspace symlinks, apps/web/dist, TS source run by tsx, and certs/ if the CA cert is present). COPY --from=builder --chown=app:app /app /app USER app EXPOSE 8080 # Container liveness. Dokploy/Traefik can additionally health-check the /api/health HTTP path. HEALTHCHECK --interval=30s --timeout=5s --start-period=25s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||8080)+'/api/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))" # server.js registers the tsx ESM loader, builds the Fastify app, and listens on 0.0.0.0:$PORT. CMD ["node", "server.js"]