From b8d2693c6706f6326287e44a22c61674503accc6 Mon Sep 17 00:00:00 2001 From: robbond Date: Fri, 6 Mar 2026 09:48:36 +0000 Subject: [PATCH 1/4] feat(deploy): add production runtime foundation --- .dockerignore | 13 ++++++++ .env.example | 20 ++++++++---- Dockerfile | 34 ++++++++++++++++++++ app/api/health/route.ts | 13 ++++++++ docker-compose.prod.yml | 23 ++++++++++++++ docs/deployment-production.md | 58 +++++++++++++++++++++++++++++++++++ 6 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 app/api/health/route.ts create mode 100644 docker-compose.prod.yml create mode 100644 docs/deployment-production.md 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..698ec4c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM node:20-alpine AS deps +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +FROM node:20-alpine AS builder +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npm run build + +FROM node:20-alpine AS prod-deps +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev + +FROM node:20-alpine 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 + +USER node + +EXPOSE 3000 + +CMD ["npm", "run", "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. -- 2.39.5 From 4a60c7ec39894b6fb9777d1bc8de6288f267bd0a Mon Sep 17 00:00:00 2001 From: robbond Date: Fri, 6 Mar 2026 09:50:44 +0000 Subject: [PATCH 2/4] chore(deploy): harden docker base image patch level --- Dockerfile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 698ec4c..ba8b59f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,29 @@ -FROM node:20-alpine AS deps +FROM node:20.19.5-alpine3.22 AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci -FROM node:20-alpine AS builder +FROM node:20.19.5-alpine3.22 AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build -FROM node:20-alpine AS prod-deps +FROM node:20.19.5-alpine3.22 AS prod-deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --omit=dev -FROM node:20-alpine AS runner +FROM node:20.19.5-alpine3.22 AS runner WORKDIR /app ENV NODE_ENV=production +RUN apk upgrade --no-cache + 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 -- 2.39.5 From cf1cd39c54b92a51a5ee6875bf460cb5c4a36b77 Mon Sep 17 00:00:00 2001 From: robbond Date: Fri, 6 Mar 2026 09:52:04 +0000 Subject: [PATCH 3/4] chore(deploy): switch to patched bookworm slim runtime base --- Dockerfile | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index ba8b59f..31ec0b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,42 @@ -FROM node:20.19.5-alpine3.22 AS deps +FROM node:20.19.5-bookworm-slim AS deps WORKDIR /app +RUN apt-get update \ + && apt-get upgrade -y \ + && rm -rf /var/lib/apt/lists/* + COPY package.json package-lock.json ./ RUN npm ci -FROM node:20.19.5-alpine3.22 AS builder +FROM node:20.19.5-bookworm-slim AS builder WORKDIR /app +RUN apt-get update \ + && apt-get upgrade -y \ + && rm -rf /var/lib/apt/lists/* + COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build -FROM node:20.19.5-alpine3.22 AS prod-deps +FROM node:20.19.5-bookworm-slim AS prod-deps WORKDIR /app +RUN apt-get update \ + && apt-get upgrade -y \ + && rm -rf /var/lib/apt/lists/* + COPY package.json package-lock.json ./ RUN npm ci --omit=dev -FROM node:20.19.5-alpine3.22 AS runner +FROM node:20.19.5-bookworm-slim AS runner WORKDIR /app ENV NODE_ENV=production -RUN apk upgrade --no-cache +RUN apt-get update \ + && apt-get upgrade -y \ + && rm -rf /var/lib/apt/lists/* COPY --from=prod-deps /app/node_modules ./node_modules COPY --from=builder /app/package.json ./package.json -- 2.39.5 From 820b676bd868f452ec52bfad70ea3ea5ce14325c Mon Sep 17 00:00:00 2001 From: robbond Date: Fri, 6 Mar 2026 09:53:15 +0000 Subject: [PATCH 4/4] chore(deploy): move runtime to chainguard node images --- Dockerfile | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/Dockerfile b/Dockerfile index 31ec0b2..6d81085 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,50 +1,32 @@ -FROM node:20.19.5-bookworm-slim AS deps +FROM cgr.dev/chainguard/node:20-dev AS deps WORKDIR /app -RUN apt-get update \ - && apt-get upgrade -y \ - && rm -rf /var/lib/apt/lists/* - COPY package.json package-lock.json ./ RUN npm ci -FROM node:20.19.5-bookworm-slim AS builder +FROM cgr.dev/chainguard/node:20-dev AS builder WORKDIR /app -RUN apt-get update \ - && apt-get upgrade -y \ - && rm -rf /var/lib/apt/lists/* - COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build -FROM node:20.19.5-bookworm-slim AS prod-deps +FROM cgr.dev/chainguard/node:20-dev AS prod-deps WORKDIR /app -RUN apt-get update \ - && apt-get upgrade -y \ - && rm -rf /var/lib/apt/lists/* - COPY package.json package-lock.json ./ RUN npm ci --omit=dev -FROM node:20.19.5-bookworm-slim AS runner +FROM cgr.dev/chainguard/node:20 AS runner WORKDIR /app ENV NODE_ENV=production -RUN apt-get update \ - && apt-get upgrade -y \ - && rm -rf /var/lib/apt/lists/* - 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 -USER node - EXPOSE 3000 -CMD ["npm", "run", "start", "--", "-H", "0.0.0.0", "-p", "3000"] \ No newline at end of file +CMD ["node", "node_modules/next/dist/bin/next", "start", "-H", "0.0.0.0", "-p", "3000"] \ No newline at end of file -- 2.39.5