diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..17938b5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,34 @@ +# Source control +.git +.gitignore + +# Build output (rebuilt inside container) +.next +out + +# Dependencies (reinstalled inside container) +node_modules + +# Local secrets — never bake into image +.env +.env.local +.env*.local + +# Dev / tooling +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Generated / large files +graphify-out +coverage +.vercel + +# OS +.DS_Store +Thumbs.db + +# Docker files themselves +Dockerfile +.dockerignore +docker-compose*.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b558f1a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,62 @@ +# ── Stage 1: install dependencies ──────────────────────────────────────────── +FROM node:20-alpine AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +COPY package.json package-lock.json* ./ +RUN npm ci + +# ── Stage 2: build ─────────────────────────────────────────────────────────── +FROM node:20-alpine AS builder +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# NEXT_PUBLIC_* vars are embedded at build time — pass them as build args. +# Server-side secrets (STRIPE_SECRET_KEY, SUPABASE_SERVICE_ROLE_KEY, etc.) +# are injected at runtime via env / docker-compose and never baked into the image. +ARG NEXT_PUBLIC_SUPABASE_URL +ARG NEXT_PUBLIC_SUPABASE_ANON_KEY +ARG NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY +ARG NEXT_PUBLIC_SITE_URL=http://localhost:3000 + +ENV NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL +ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY +ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=$NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY +ENV NEXT_PUBLIC_SITE_URL=$NEXT_PUBLIC_SITE_URL + +ENV NEXT_TELEMETRY_DISABLED=1 +ENV NODE_ENV=production + +RUN npm run build + +# ── Stage 3: production runner ──────────────────────────────────────────────── +FROM node:20-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +# Create a non-root user (security best practice) +RUN addgroup --system --gid 1001 nodejs \ + && adduser --system --uid 1001 nextjs + +# Copy public assets +COPY --from=builder /app/public ./public + +# standalone output: server.js + chunk bundles (no full node_modules) +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +# Prepare prerender cache dir with correct ownership +RUN mkdir -p .next && chown nextjs:nodejs .next + +USER nextjs + +EXPOSE 3000 +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 + +# Next.js standalone entry point +CMD ["node", "server.js"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3c1e0aa --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,55 @@ +services: + + # ── Next.js app ───────────────────────────────────────────────────────────── + flatrender: + build: + context: . + dockerfile: Dockerfile + args: + # NEXT_PUBLIC_* must be available at BUILD time (embedded in JS bundle). + # Supply them here or via a .env file next to docker-compose.yml. + NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${NEXT_PUBLIC_SUPABASE_ANON_KEY} + NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY:-} + NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-https://flatrender.com} + container_name: flatrender + restart: unless-stopped + ports: + - "3000:3000" + environment: + NODE_ENV: production + + # ── Server-side secrets (runtime only, never in image) ─────────────── + SUPABASE_SERVICE_ROLE_KEY: ${SUPABASE_SERVICE_ROLE_KEY} + STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-} + STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET:-} + STRIPE_PRICE_PRO_MONTHLY: ${STRIPE_PRICE_PRO_MONTHLY:-} + STRIPE_PRICE_PRO_ANNUAL: ${STRIPE_PRICE_PRO_ANNUAL:-} + STRIPE_PRICE_BUSINESS_MONTHLY: ${STRIPE_PRICE_BUSINESS_MONTHLY:-} + STRIPE_PRICE_BUSINESS_ANNUAL: ${STRIPE_PRICE_BUSINESS_ANNUAL:-} + + # ── Optional integrations ───────────────────────────────────────────── + # Point to admin-api container if running together + ADMIN_API_URL: ${ADMIN_API_URL:-http://flatrender-api:5000} + REMOVE_BG_API_KEY: ${REMOVE_BG_API_KEY:-} + REMBG_SERVICE_URL: ${REMBG_SERVICE_URL:-} + + # ── Render worker (optional) ────────────────────────────────────────── + RENDER_WORKER_URL: ${RENDER_WORKER_URL:-http://render-worker:3355} + RENDER_WORKER_SECRET: ${RENDER_WORKER_SECRET:-} + RENDER_MOCK: ${RENDER_MOCK:-true} + depends_on: [] + + # ── Render worker (optional, uncomment if using nexrender) ────────────────── + # render-worker: + # build: + # context: . + # dockerfile: Dockerfile.worker + # container_name: flatrender-worker + # restart: unless-stopped + # ports: + # - "3355:3355" + # environment: + # RENDER_WORKER_PORT: 3355 + # RENDER_WORKER_SECRET: ${RENDER_WORKER_SECRET:-} + # NEXRENDER_BINARY: ${NEXRENDER_BINARY:-}