From 1b3ae078111e71cd1799aa898abd9eb4b20e5626 Mon Sep 17 00:00:00 2001 From: Do Siki Date: Fri, 11 Sep 2026 22:12:01 +0200 Subject: [PATCH] feat(deploy): staging/production Docker deploy for Payload (MITHOME-97) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes a real, previously-undiscovered build failure and a bigger architectural gap found while testing an actual `docker build` of proto/Dockerfile for the first time since the Payload migration: 1. Docker build failure: the (frontend)/[locale] pages use generateStaticParams, so `next build` fully prerenders them (SSG) — which calls the Payload Local API during the build. With no MONGODB_URI/PAYLOAD_SECRET reachable in the build stage, `docker build` failed outright ("missing secret key"). 2. Bigger problem underneath: even if the build could reach a DB, full SSG means a Payload admin edit would NOT appear on the public site until a full rebuild + redeploy — directly undermining the project's whole reason for migrating to Payload (self-service content editing for the client). Fix (user-confirmed direction: force-dynamic): dropped generateStaticParams from (frontend)/[locale]/layout.tsx and [locale]/[slug]/page.tsx, added `export const dynamic = 'force-dynamic'` to layout.tsx + both page.tsx files. Every request now reads Payload live — publishing in the admin is visible immediately, and the Docker build no longer needs any DB connectivity at all (verified: a full `docker build --target builder` now succeeds with zero env vars set). Docker/Compose changes: - docker-compose.staging.yml / docker-compose.prod.yml: wired PAYLOAD_SECRET through to the app container (was documented in .env.*.example since MITHOME-86 but never actually passed to the container — Payload would have refused to start). No fallback, same fail-loudly pattern as MONGODB_URI. - Same two files: added a named `media_data_{staging,prod}` volume mounted at /app/media — Payload's local upload storage (Media.ts) writes there at the container's runtime cwd; without a volume, `deploy.sh`'s `--force-recreate` would silently wipe every uploaded logo/image on each deploy. - docker-compose.dev.yml: was missing PAYLOAD_SECRET entirely (only discovered because the same "missing secret key" error reproduces there too) — added a dev-only literal value. Media persistence already works there via the existing `./proto:/app` bind mount, no volume needed. Also dropped the obsolete `version: '3.8'` key (compose warns it's ignored). - DOCKER.md: one-paragraph note on the new PAYLOAD_SECRET requirement and where the admin account gets created. Verified: - `docker build --target builder` succeeds from a clean context with zero environment variables (previously failed). - `docker build --target runner` + `docker run` against the real dev MongoDB (PAYLOAD_SECRET + MONGODB_URI supplied at runtime only): /hu, /admin and /api/health all return 200 inside the container; confirmed live in the browser that Payload content renders correctly end-to-end through the production Next.js server, not just `next dev`. - `docker compose -f docker-compose.{staging,prod,dev}.yml config` parses cleanly. - Full gate green: tsc, lint, proto unit tests (51 passed), scripts/pre-deploy-tests.sh (proto tests, tsc, lint, content schema, plane-sync — all pass). deploy.sh itself needs no changes: it already just runs `docker compose up --build`, and that now works without any build-time DB wiring. Co-Authored-By: Claude Sonnet 5 --- DOCKER.md | 2 ++ docker-compose.dev.yml | 5 +++-- docker-compose.prod.yml | 9 +++++++++ docker-compose.staging.yml | 11 +++++++++++ proto/src/app/(frontend)/[locale]/[slug]/page.tsx | 13 ++++--------- proto/src/app/(frontend)/[locale]/layout.tsx | 13 +++++++++---- proto/src/app/(frontend)/[locale]/page.tsx | 3 +++ 7 files changed, 41 insertions(+), 15 deletions(-) diff --git a/DOCKER.md b/DOCKER.md index 4a833a0..eb9d3a1 100755 --- a/DOCKER.md +++ b/DOCKER.md @@ -37,6 +37,8 @@ A Docker stack a következő szolgáltatásokat indítja: - Username: `admin` - Password: `admin123` +**Payload CMS admin** (http://localhost:8080/admin): nincs előre létrehozott felhasználó — az első betöltéskor a Payload felkínálja az admin fiók létrehozását. A `docker-compose.dev.yml` egy fix, nem titkos `PAYLOAD_SECRET`-et ad át (MITHOME-97) — staging/production környezetben ez kötelezően a saját `.env.` fájlból jön, nincs alapértelmezett érték (a konténer el sem indul nélküle). + ## 📊 MongoDB Hozzáférés ### 1. Mongo Express Web UI diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 01a074b..4a4fb43 100755 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: # Next.js Application app: @@ -18,6 +16,9 @@ services: # with "Command find requires authentication" (MITHOME-98). - MONGODB_URI=mongodb://admin:password123@mongodb:27017/mozdit?authSource=admin - MONGODB_DB=mozdit + # Dev-only, nem titkos (MITHOME-97) — Payload ez nélkül el sem indul + # ("missing secret key"), és ez a compose fájl eddig nem adta át. + - PAYLOAD_SECRET=dev-only-insecure-secret-do-not-use-in-staging-or-prod - NEXT_PUBLIC_SITE_URL=http://localhost:8080 - NEXT_PUBLIC_COMPANY_NAME=mozdIT Bt. - NEXT_PUBLIC_CONTACT_EMAIL=info@mozdit.hu diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 76640de..817b4c8 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -17,10 +17,17 @@ services: # unauthenticated default URI would silently break the app — fail loudly instead. - MONGODB_URI=${MONGODB_URI} - MONGODB_DB=${MONGODB_DB:-mozdit} + # No fallback either (MITHOME-97): Payload refuses to start without a real + # secret ("missing secret key"), which is exactly what we want here. + - PAYLOAD_SECRET=${PAYLOAD_SECRET} - NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL:-https://mozdit.hu} - NEXT_PUBLIC_COMPANY_NAME=${NEXT_PUBLIC_COMPANY_NAME:-mozdIT Bt.} - NEXT_PUBLIC_CONTACT_EMAIL=${NEXT_PUBLIC_CONTACT_EMAIL:-info@mozdit.hu} - LOKI_HOST=${LOKI_HOST:-http://loki:3100} + volumes: + # Payload helyi upload storage — lásd docker-compose.staging.yml azonos + # kommentjét (MITHOME-97). + - media_data_prod:/app/media depends_on: - mongodb networks: @@ -52,6 +59,8 @@ services: volumes: mongodb_data_prod: driver: local + media_data_prod: + driver: local networks: mozdit-network: diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml index 1535fb5..9102aeb 100644 --- a/docker-compose.staging.yml +++ b/docker-compose.staging.yml @@ -18,11 +18,20 @@ services: # unauthenticated default URI would silently break the app — fail loudly instead. - MONGODB_URI=${MONGODB_URI} - MONGODB_DB=${MONGODB_DB:-mozdit} + # No fallback either (MITHOME-97): Payload refuses to start without a real + # secret ("missing secret key"), which is exactly what we want here. + - PAYLOAD_SECRET=${PAYLOAD_SECRET} - NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL:-https://stage.mozdit.hu} - NEXT_PUBLIC_DEPLOY_ENV=staging - NEXT_PUBLIC_COMPANY_NAME=${NEXT_PUBLIC_COMPANY_NAME:-mozdIT Bt.} - NEXT_PUBLIC_CONTACT_EMAIL=${NEXT_PUBLIC_CONTACT_EMAIL:-info@mozdit.hu} - LOKI_HOST=${LOKI_HOST:-http://loki:3100} + volumes: + # Payload helyi upload storage (proto/src/collections/Media.ts) — futásidőben + # a konténer WORKDIR-je (/app) alatti media/ mappába ír. Named volume nélkül + # a `deploy.sh` minden újratelepítéskor (--force-recreate) elveszítené a + # korábban feltöltött logókat/képeket (MITHOME-97). + - media_data_staging:/app/media depends_on: - mongodb networks: @@ -54,6 +63,8 @@ services: volumes: mongodb_data_staging: driver: local + media_data_staging: + driver: local networks: mozdit-network-staging: diff --git a/proto/src/app/(frontend)/[locale]/[slug]/page.tsx b/proto/src/app/(frontend)/[locale]/[slug]/page.tsx index 6b7b614..f76740a 100644 --- a/proto/src/app/(frontend)/[locale]/[slug]/page.tsx +++ b/proto/src/app/(frontend)/[locale]/[slug]/page.tsx @@ -4,7 +4,7 @@ import AboutView from '@/components/views/AboutView' import ServicesView from '@/components/views/ServicesView' import ContactView from '@/components/views/ContactView' import LegalPageView from '@/components/views/LegalPageView' -import { LOCALES, PAGE_SLUGS, resolvePageKey, isLocale, type Locale, type PageKey } from '@/lib/i18n' +import { resolvePageKey, isLocale, type Locale } from '@/lib/i18n' import { getAboutContent, getServicesContent, @@ -17,14 +17,9 @@ import { siteConfig, getOgLocale } from '@/config/site' type Params = { locale: string; slug: string } -export function generateStaticParams() { - return LOCALES.flatMap((locale) => - (Object.keys(PAGE_SLUGS) as PageKey[]).map((key) => ({ - locale, - slug: PAGE_SLUGS[key][locale], - })) - ) -} +// WHY force-dynamic: lásd ../layout.tsx — nincs generateStaticParams, minden +// kérés élőben olvassa a Payload-ot, admin publikálás azonnal látszik. +export const dynamic = 'force-dynamic' const LEGAL_META: Record<'privacy' | 'terms', Record> = { privacy: { diff --git a/proto/src/app/(frontend)/[locale]/layout.tsx b/proto/src/app/(frontend)/[locale]/layout.tsx index 07447fb..b5da35d 100644 --- a/proto/src/app/(frontend)/[locale]/layout.tsx +++ b/proto/src/app/(frontend)/[locale]/layout.tsx @@ -1,13 +1,18 @@ import { notFound } from 'next/navigation' import Header from '../../../components/Header' import Footer from '../../../components/Footer' -import { LOCALES, isLocale, localePath, type Locale } from '@/lib/i18n' +import { isLocale, localePath, type Locale } from '@/lib/i18n' import { getCommonContent, getHomeContent } from '@/lib/payload-content' import { siteConfig, getMainNavigation, getFooterNavigation, getFooterLegalLinks, getSiteDescription } from '@/config/site' -export function generateStaticParams() { - return LOCALES.map((locale) => ({ locale })) -} +// WHY force-dynamic (MITHOME-97): ezek az oldalak a Payload Local API-t hívják +// (élő MongoDB-olvasás). generateStaticParams + SSG mellett a build időben +// sütött ki minden oldal, és a Payload adminban végzett publikálás csak egy +// teljes redeploy után jelent volna meg a publikus oldalon — ez pont az +// ellentéte az önkiszolgáló szerkesztés céljának, amiért a Payload-migráció +// történt. force-dynamic-kal minden kérés friss Payload-olvasást kap, és a +// Docker build sem függ többé egy build-idejű MongoDB-kapcsolattól. +export const dynamic = 'force-dynamic' export default async function LocaleLayout({ children, diff --git a/proto/src/app/(frontend)/[locale]/page.tsx b/proto/src/app/(frontend)/[locale]/page.tsx index 428ad9c..dcc08d0 100755 --- a/proto/src/app/(frontend)/[locale]/page.tsx +++ b/proto/src/app/(frontend)/[locale]/page.tsx @@ -7,6 +7,9 @@ import { notFound } from 'next/navigation' type Params = { locale: string } +// WHY force-dynamic: lásd [slug]/page.tsx és ../layout.tsx. +export const dynamic = 'force-dynamic' + export async function generateMetadata({ params }: { params: Promise }): Promise { const { locale: rawLocale } = await params if (!isLocale(rawLocale)) return {}