diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..65ae2b2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +node_modules +.next +.git +*.log +logs +.env +.env.* +!.env.example +.env.local +.env.*.local +.vscode +.idea +.DS_Store diff --git a/.env.example b/.env.example index 6e36439..a90c200 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,22 @@ -NEXT_PUBLIC_SUPABASE_URL= -NEXT_PUBLIC_SUPABASE_ANON_KEY= -SUPABASE_SERVICE_ROLE_KEY= +NODE_ENV=production NEXT_PUBLIC_APP_URL=http://localhost:3000 -JOB_SECRET= -CRON_SECRET= -ENCRYPTION_KEY= +NEXT_PUBLIC_SUPABASE_URL= +NEXT_PUBLIC_SUPABASE_ANON_KEY= +SUPABASE_URL= +SUPABASE_ANON_KEY= +SUPABASE_SERVICE_ROLE_KEY= + PLESK_CRED_ENC_KEY= STRIPE_SECRET_KEY= STRIPE_WEBHOOK_SECRET= + +CRON_SHARED_SECRET= + +# Existing runtime keys used by current codebase +JOB_SECRET= +CRON_SECRET= +ENCRYPTION_KEY= STRIPE_PRICE_ID= diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6d81085 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM cgr.dev/chainguard/node:20-dev AS deps +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +FROM cgr.dev/chainguard/node:20-dev AS builder +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npm run build + +FROM cgr.dev/chainguard/node:20-dev AS prod-deps +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev + +FROM cgr.dev/chainguard/node:20 AS runner +WORKDIR /app + +ENV NODE_ENV=production + +COPY --from=prod-deps /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./package.json +COPY --from=builder /app/next.config.mjs ./next.config.mjs +COPY --from=builder /app/.next ./.next + +EXPOSE 3000 + +CMD ["node", "node_modules/next/dist/bin/next", "start", "-H", "0.0.0.0", "-p", "3000"] \ No newline at end of file diff --git a/app/api/health/route.ts b/app/api/health/route.ts new file mode 100644 index 0000000..931470c --- /dev/null +++ b/app/api/health/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from "next/server"; + +export async function GET() { + return NextResponse.json( + { + ok: true, + service: "plesk-agency-portal", + timestamp: new Date().toISOString(), + environment: process.env.NODE_ENV ?? "development", + }, + { status: 200 }, + ); +} diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..79620cb --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,23 @@ +services: + web: + container_name: plesk-agency-portal + build: + context: . + dockerfile: Dockerfile + restart: unless-stopped + env_file: + - .env + ports: + - "3000:3000" + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "fetch('http://127.0.0.1:3000/api/health').then((r)=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))", + ] + interval: 30s + timeout: 5s + retries: 3 + start_period: 20s diff --git a/docs/deployment-production.md b/docs/deployment-production.md new file mode 100644 index 0000000..50ffb2f --- /dev/null +++ b/docs/deployment-production.md @@ -0,0 +1,58 @@ +# Production Deployment Runtime Foundation + +This project can be deployed in a dedicated Proxmox container using Docker Compose. The files added here establish a minimal production runtime baseline without changing application business logic or Supabase security boundaries. + +## Dockerfile purpose + +The `Dockerfile` uses a multi-stage build on Node 20: + +- installs dependencies +- runs `next build` +- prepares a lean runtime image with production dependencies +- runs the app with `next start` on port `3000` + +This keeps the runtime image small and focused on serving the built Next.js app. + +## docker-compose.prod.yml purpose + +`docker-compose.prod.yml` defines one service: + +- service name: `web` +- container name: `plesk-agency-portal` +- restart policy: `unless-stopped` +- environment from `.env` +- port mapping `3000:3000` +- health check against `/api/health` + +## External Supabase requirement + +Supabase remains external to this application container. The app container must only connect to Supabase via environment variables. No local Supabase container is included in this production compose file. + +## Health endpoint usage + +`GET /api/health` provides a fast, dependency-light runtime check for container orchestration and uptime checks. + +Example: + +```bash +curl -s http://localhost:3000/api/health +``` + +Expected response shape: + +- `ok: true` +- `service: "plesk-agency-portal"` +- `timestamp` +- `environment` + +## Production environment expectations + +Before deploying, set production-safe values in `.env` (using `.env.example` as the template), including: + +- app URL and `NODE_ENV` +- Supabase public and server credentials +- Plesk credential encryption key +- Stripe secrets +- cron/job shared secrets + +Keep secrets out of source control.