From f1d02272606d11a96e18c25f2fd51db08aeda584 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Wed, 4 Mar 2026 17:57:01 +0100 Subject: [PATCH] fix(deploy): auto-run payload migrations on deploy, hide cms error details from visitors --- .gitea/workflows/deploy.yml | 54 ++++++++++++++++------------ Dockerfile | 22 ++++++++---- components/CMSConnectivityNotice.tsx | 10 +++--- 3 files changed, 52 insertions(+), 34 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index fbf4c953..52537b14 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -226,6 +226,21 @@ jobs: tags: registry.infra.mintel.me/mintel/klz-2026:${{ needs.prepare.outputs.image_tag }} secrets: | NPM_TOKEN=${{ secrets.NPM_TOKEN }} + - name: 🔄 Build and Push Migrator + uses: docker/build-push-action@v5 + with: + context: . + push: true + provenance: false + platforms: linux/amd64 + target: migrator + build-args: | + NEXT_PUBLIC_BASE_URL=${{ needs.prepare.outputs.next_public_url }} + NEXT_PUBLIC_TARGET=${{ needs.prepare.outputs.target }} + NPM_TOKEN=${{ secrets.NPM_TOKEN }} + tags: registry.infra.mintel.me/mintel/klz-2026:migrate-${{ needs.prepare.outputs.image_tag }} + secrets: | + NPM_TOKEN=${{ secrets.NPM_TOKEN }} # ────────────────────────────────────────────────────────────────────────────── # JOB 4: Deploy @@ -396,31 +411,24 @@ jobs: REMOTE_DB_USER="${REMOTE_DB_USER:-payload}" REMOTE_DB_NAME="${REMOTE_DB_NAME:-payload}" - # Auto-detect migrations from src/migrations/*.ts - BATCH=1 - VALUES="" - for f in $(ls src/migrations/*.ts 2>/dev/null | sort); do - NAME=$(basename "$f" .ts) - [ -n "$VALUES" ] && VALUES="$VALUES," - VALUES="$VALUES ('$NAME', $BATCH)" - ((BATCH++)) - done - - if [ -n "$VALUES" ]; then - echo " - DO \$\$ BEGIN - DELETE FROM payload_migrations WHERE batch = -1; - INSERT INTO payload_migrations (name, batch) - SELECT name, batch FROM (VALUES $VALUES) AS v(name, batch) - WHERE NOT EXISTS (SELECT 1 FROM payload_migrations pm WHERE pm.name = v.name); - EXCEPTION WHEN undefined_table THEN - RAISE NOTICE 'payload_migrations table does not exist yet — skipping sanitization'; - END \$\$; - " | ssh root@alpha.mintel.me "docker exec -i $DB_CONTAINER psql -U $REMOTE_DB_USER -d $REMOTE_DB_NAME" - fi + # Run Payload migrations via a temporary container before restarting the app. + # This ensures fresh branch deployments (empty DBs) get their schema on first deploy. + echo "🔄 Running Payload migrations..." + MIGRATOR_IMAGE="registry.infra.mintel.me/mintel/klz-2026:migrate-$IMAGE_TAG" + + ssh root@alpha.mintel.me " + echo '${{ steps.auth.outputs.token }}' | docker login registry.infra.mintel.me -u '${{ steps.auth.outputs.user }}' --password-stdin 2>/dev/null || true + docker pull $MIGRATOR_IMAGE + docker run --rm \ + --network ${PROJECT_NAME}_default \ + --env-file $SITE_DIR/$ENV_FILE \ + $MIGRATOR_IMAGE \ + && echo '✅ Migrations complete.' \ + || echo '⚠️ Migrations failed or already up-to-date — continuing.' + " # Restart app to pick up clean migration state - APP_CONTAINER="${{ needs.prepare.outputs.project_name }}-klz-app-1" + APP_CONTAINER="${PROJECT_NAME}-klz-app-1" ssh root@alpha.mintel.me "docker restart $APP_CONTAINER" ssh root@alpha.mintel.me "docker system prune -f --filter 'until=24h'" diff --git a/Dockerfile b/Dockerfile index 7283dfdd..1de99a77 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,14 +5,12 @@ WORKDIR /app # Arguments for build-time configuration ARG NEXT_PUBLIC_BASE_URL ARG NEXT_PUBLIC_TARGET -ARG DIRECTUS_URL ARG UMAMI_WEBSITE_ID ARG UMAMI_API_ENDPOINT # Environment variables for Next.js build ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL ENV NEXT_PUBLIC_TARGET=$NEXT_PUBLIC_TARGET -ENV DIRECTUS_URL=$DIRECTUS_URL ENV UMAMI_WEBSITE_ID=$UMAMI_WEBSITE_ID ENV UMAMI_API_ENDPOINT=$UMAMI_API_ENDPOINT ENV SKIP_RUNTIME_ENV_VALIDATION=true @@ -55,11 +53,6 @@ RUN pnpm build FROM git.infra.mintel.me/mmintel/runtime:latest AS runner WORKDIR /app -# Create nextjs user and group (standardized in runtime image but ensuring local ownership) -USER root -RUN chown -R nextjs:nodejs /app -USER nextjs - ENV HOSTNAME="0.0.0.0" ENV PORT=3000 ENV NODE_ENV=production @@ -71,3 +64,18 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/.next/cache ./.next/cache CMD ["node", "server.js"] + +# ────────────────────────────────────────────────────────────────────────────── +# Stage: Migrator — used by CI to run "payload migrate" against the live DB. +# Retains node_modules so the payload CLI is available. +# ────────────────────────────────────────────────────────────────────────────── +FROM node:20-alpine AS migrator +WORKDIR /app +ENV NODE_ENV=production + +COPY --from=builder /app/package.json ./package.json +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/src/migrations ./src/migrations +COPY --from=builder /app/payload.config.ts ./payload.config.ts + +CMD ["node", "node_modules/.bin/payload", "migrate"] diff --git a/components/CMSConnectivityNotice.tsx b/components/CMSConnectivityNotice.tsx index a24545b5..5ebc9896 100644 --- a/components/CMSConnectivityNotice.tsx +++ b/components/CMSConnectivityNotice.tsx @@ -15,10 +15,12 @@ export default function CMSConnectivityNotice() { const isDebug = new URLSearchParams(window.location.search).has('cms_debug'); const isLocal = config.isDevelopment; const isTesting = config.isTesting; + const target = process.env.NEXT_PUBLIC_TARGET || ''; + const isBranch = target === 'branch'; - // Only proceed with check if it's developer context (Local or Testing) + // Only proceed with check if it's developer context (Local, Testing, or Branch preview) // Staging and Production should NEVER see this unless forced with ?cms_debug - if (!isLocal && !isTesting && !isDebug) return; + if (!isLocal && !isTesting && !isBranch && !isDebug) return; try { const response = await fetch('/api/health/cms'); @@ -58,8 +60,8 @@ export default function CMSConnectivityNotice() {

CMS Issue Detected

{errorMsg === 'relation "products" does not exist' - ? 'The database schema is missing. Please sync your local data to this environment.' - : errorMsg || 'The application cannot connect to the Directus CMS.'} + ? 'The database schema is missing. Please run migrations for this environment.' + : 'A content service is unavailable. Check the deployment logs for details.'}