Compare commits

...

8 Commits

Author SHA1 Message Date
eeb0920afd chore: stabilize deployment and resolve asset 404s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🏗️ Build (push) Failing after 12m38s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Smoke Test (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-11 21:55:24 +02:00
f8eec731c0 fix: final YAML syntax repair for docker-compose.yml (v1.15.14)
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🏗️ Build (push) Failing after 20m27s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-04-11 19:33:25 +02:00
8b80af3d1a fix: repair YAML indentation in docker-compose.yml (v1.15.13)
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🏗️ Build (push) Failing after 21m34s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-11 19:32:19 +02:00
de42890476 fix: override CMD to apps/web/server.js (v1.15.12)
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🏗️ Build (push) Successful in 14m35s
Build & Deploy / 🚀 Deploy (push) Failing after 12s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-11 19:12:51 +02:00
fbd33da925 fix: stabilize build-time DB connection (v1.15.11)
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🏗️ Build (push) Successful in 13m25s
Build & Deploy / 🚀 Deploy (push) Failing after 13s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-11 18:06:45 +02:00
f3bab24bd3 feat: enforced CI deployment healthcheck waiting (zero-downtime)
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 17s
Build & Deploy / 🏗️ Build (push) Failing after 1h24m30s
Nightly QA / 🔍 Static Analysis (push) Successful in 2m9s
Nightly QA / 🔗 Links & Deps (push) Successful in 2m45s
Nightly QA / 🎭 Lighthouse (push) Successful in 4m24s
Nightly QA / 📝 E2E (push) Successful in 5m13s
Nightly QA / 🔔 Notify (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
2026-04-10 23:13:13 +02:00
3b668fc876 fix: correct standalone entrypoint - apps/web/server.js (v1.15.9)
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 39s
Build & Deploy / 🏗️ Build (push) Failing after 2h26m35s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
2026-04-10 23:11:15 +02:00
2827239796 chore: OOM hardening - lower heap to 4GB, disable sourcemaps (v1.15.8)
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 15s
Build & Deploy / 🏗️ Build (push) Successful in 23m12s
Build & Deploy / 🚀 Deploy (push) Successful in 18s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-10 14:26:19 +02:00
4 changed files with 77 additions and 9 deletions

View File

@@ -172,7 +172,7 @@ jobs:
S3_BUCKET=${{ secrets.S3_BUCKET || vars.S3_BUCKET || 'mintel' }}
S3_REGION=${{ secrets.S3_REGION || vars.S3_REGION || 'fsn1' }}
S3_PREFIX=${{ secrets.S3_PREFIX || vars.S3_PREFIX || 'mintel.me' }}
DATABASE_URI=${{ secrets.DATABASE_URI || (needs.prepare.outputs.target == 'testing' && secrets.TESTING_DIRECTUS_DB_PASSWORD && format('postgres://directus:{0}@postgres-db:5432/directus', secrets.TESTING_DIRECTUS_DB_PASSWORD)) || 'postgres://payload:payload@postgres-db:5432/payload' }}
DATABASE_URI=${{ secrets.DATABASE_URI || (needs.prepare.outputs.target == 'testing' && secrets.TESTING_DIRECTUS_DB_PASSWORD && format('postgres://directus:{0}@postgres-db:5432/directus', secrets.TESTING_DIRECTUS_DB_PASSWORD)) || 'postgres://payload:payload@127.0.0.1:5432/payload' }}
PAYLOAD_SECRET=${{ secrets.PAYLOAD_SECRET || 'secret' }}
BUILD_ID=${{ github.sha }}
tags: registry.infra.mintel.me/mintel/mintel.me:${{ needs.prepare.outputs.image_tag }}
@@ -388,7 +388,7 @@ jobs:
docker volume create 'mintel-me_payload-db-data' || true
cd $SITE_DIR
docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file $ENV_FILE pull
docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file $ENV_FILE up -d --remove-orphans
docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file $ENV_FILE up -d --wait --remove-orphans
"
- name: 🧹 Purge S3 Cache
@@ -418,14 +418,60 @@ jobs:
if: always()
run: docker builder prune -f --filter "until=1h"
# (JOB 5: Post-Deploy Verification was removed to reduce pipeline noise)
# ──────────────────────────────────────────────────────────────────────────────
# JOB 5: Post-Deploy Verification
# ──────────────────────────────────────────────────────────────────────────────
post_deploy_checks:
name: 🩺 Smoke Test
needs: [prepare, deploy]
runs-on: docker
if: needs.deploy.result == 'success'
container:
image: alpine:latest
steps:
- name: 🌐 Check Production URL
shell: sh
run: |
# Wait longer (up to 2 minutes) for Next.js to fully prime
COUNT=0
MAX=24
URL="${{ needs.prepare.outputs.next_public_url }}"
echo "Verifying $URL is responsive..."
while [ $COUNT -lt $MAX ]; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$URL/" || echo "000")
if [ "$STATUS" = "200" ]; then
echo "✅ Site is UP (200 OK)"
break
fi
echo "⏳ Wait for 200 OK (Status: $STATUS)..."
sleep 5
COUNT=$((COUNT + 1))
done
if [ "$STATUS" != "200" ]; then
echo "❌ Site failed smoke test after 2 minutes! (Status: $STATUS)"
exit 1
fi
- name: 🌐 Check Case Study Assets
shell: sh
run: |
URL="${{ needs.prepare.outputs.next_public_url }}/case-studies/klz-cables"
echo "Verifying case study $URL..."
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$URL")
if [ "$STATUS" != "200" ]; then
echo "❌ Case study page failed! (Status: $STATUS)"
exit 1
fi
echo "✅ Case study page is UP"
# ──────────────────────────────────────────────────────────────────────────────
# JOB 6: Notifications
# ──────────────────────────────────────────────────────────────────────────────
notifications:
name: 🔔 Notify
needs: [prepare, deploy]
needs: [prepare, deploy, post_deploy_checks]
if: always()
runs-on: docker
container:

View File

@@ -31,6 +31,9 @@ ENV DATABASE_URI=$DATABASE_URI
ENV PAYLOAD_SECRET=$PAYLOAD_SECRET
ENV SKIP_RUNTIME_ENV_VALIDATION=true
ENV NEXT_BUILD_WORKERS=1
ENV NEXT_DISABLE_SOURCEMAPS=true
ENV SENTRY_SKIP_LOCAL_SOURCES=true
ENV NEXT_PRIVATE_LOCAL_WEBPACK=true
ENV CI=true
# Copy manifest files specifically for better layer caching
@@ -60,8 +63,8 @@ RUN echo "Building with ID: ${BUILD_ID}"
COPY . .
# Build application (monorepo filter)
ENV NODE_OPTIONS="--max_old_space_size=8192"
RUN pnpm --filter @mintel/web build
ENV NODE_OPTIONS="--max_old_space_size=4096"
RUN NEXT_BUILD_WORKERS=1 pnpm --filter @mintel/web build
# Stage 2: Runner
FROM git.infra.mintel.me/mmintel/runtime:latest AS runner
@@ -85,4 +88,4 @@ USER nextjs
# Start from the app directory to ensure references solve correctly
# In Standalone mode, Next.js expects node_modules and public relative to the server.js
WORKDIR /app
CMD ["node", "server.js"]
CMD ["node", "apps/web/server.js"]

View File

@@ -41,6 +41,18 @@ const nextConfig = {
return [
// Umami proxy rewrite handled in app/stats/api/send/route.ts
// Sentry relay handled in app/errors/api/relay/route.ts
{
source: '/assets/:path*',
destination: '/showcase/klz-cables.com/assets/:path*',
},
{
source: '/wp-content/:path*',
destination: '/showcase/klz-cables.com/assets/klz-cables.com/wp-content/:path*',
},
{
source: '/wp-includes/:path*',
destination: '/showcase/klz-cables.com/assets/klz-cables.com/wp-includes/:path*',
},
];
},
async redirects() {

View File

@@ -2,6 +2,7 @@ services:
mintel-me-app:
image: registry.infra.mintel.me/mintel/mintel.me:${IMAGE_TAG:-latest}
restart: always
command: node apps/web/server.js
networks:
- default
- infra
@@ -12,7 +13,6 @@ services:
# HTTP ⇒ HTTPS redirect
- 'traefik.http.routers.${PROJECT_NAME}-web.rule=${TRAEFIK_HOST_RULE:-Host("${TRAEFIK_HOST:-mintel.localhost}")}'
- "traefik.http.routers.${PROJECT_NAME}-web.entrypoints=web"
# - "traefik.http.routers.${PROJECT_NAME}-web.middlewares=redirect-https"
# HTTPS router (Standard)
- 'traefik.http.routers.${PROJECT_NAME}.rule=${TRAEFIK_HOST_RULE:-Host("${TRAEFIK_HOST:-mintel.localhost}")}'
- "traefik.http.routers.${PROJECT_NAME}.entrypoints=${TRAEFIK_ENTRYPOINT:-web}"
@@ -26,7 +26,7 @@ services:
- "caddy.reverse_proxy={{upstreams 3000}}"
# Public Router (Whitelist for OG Images, Sitemaps, Health)
- 'traefik.http.routers.${PROJECT_NAME}-public.rule=(${TRAEFIK_HOST_RULE:-Host("${TRAEFIK_HOST:-mintel.localhost}")}) && (PathPrefix("/health") || PathPrefix("/api/health") || PathPrefix("/sitemap.xml") || PathPrefix("/robots.txt") || PathPrefix("/manifest.webmanifest") || PathPrefix("/api/og") || PathRegexp(".*opengraph-image.*") || PathRegexp(".*sitemap.*"))'
- 'traefik.http.routers.${PROJECT_NAME}-public.rule=(${TRAEFIK_HOST_RULE:-Host("${TRAEFIK_HOST:-mintel.localhost}")}) && (PathPrefix("/health") || PathPrefix("/api/health") || PathPrefix("/sitemap.xml") || PathPrefix("/robots.txt") || PathPrefix("/manifest.webmanifest") || PathPrefix("/api/og") || PathPrefix("/assets") || PathPrefix("/wp-content") || PathPrefix("/wp-includes") || PathPrefix("/showcase") || PathRegexp(".*opengraph-image.*") || PathRegexp(".*sitemap.*"))'
- "traefik.http.routers.${PROJECT_NAME}-public.entrypoints=${TRAEFIK_ENTRYPOINT:-web}"
- "traefik.http.routers.${PROJECT_NAME}-public.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-}"
- "traefik.http.routers.${PROJECT_NAME}-public.tls=${TRAEFIK_TLS:-false}"
@@ -36,6 +36,7 @@ services:
# Middlewares
- "traefik.http.middlewares.${PROJECT_NAME}-ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.${PROJECT_NAME}-ratelimit.ratelimit.burst=200"
- "traefik.http.middlewares.${PROJECT_NAME}-ratelimit.ratelimit.burst=50"
# Gatekeeper Router (Path-based)
@@ -53,6 +54,12 @@ services:
# Forwarded Headers
- "traefik.http.middlewares.${PROJECT_NAME}-forward.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.middlewares.${PROJECT_NAME}-forward.headers.customrequestheaders.X-Forwarded-Ssl=on"
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/ || exit 1"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
gatekeeper:
profiles: ["gatekeeper"]