diff --git a/apps/app/Dockerfile b/apps/app/Dockerfile index 0f4929ac8..97e83b6c6 100644 --- a/apps/app/Dockerfile +++ b/apps/app/Dockerfile @@ -13,8 +13,8 @@ ENV PORT=3000 ENV HOSTNAME="0.0.0.0" ENV NODE_TLS_REJECT_UNAUTHORIZED=0 -# Copy the complete standalone build from CodeBuild -COPY .next/standalone ./ +# Copy the standalone build files (already prepared in container-build/) +COPY . ./ # Create non-root user for security RUN addgroup -g 1001 -S nodejs && \ @@ -25,5 +25,5 @@ USER nextjs EXPOSE 3000 -# Use node to run the standalone server - handle monorepo structure -CMD ["sh", "-c", "if [ -f apps/app/server.js ]; then node apps/app/server.js; elif [ -f server.js ]; then node server.js; elif [ -f index.js ]; then node index.js; else echo 'No entry point found' && exit 1; fi"] \ No newline at end of file +# Use node to run the standalone server +CMD ["node", "server.js"] \ No newline at end of file diff --git a/apps/app/buildspec.yml b/apps/app/buildspec.yml index 39e165825..b6afee6a4 100644 --- a/apps/app/buildspec.yml +++ b/apps/app/buildspec.yml @@ -16,6 +16,7 @@ phases: # Environment setup - export PATH="/root/.bun/bin:$PATH" - export PGSSLMODE=require + - export NODE_ENV=production - export NEXT_TELEMETRY_DISABLED=1 - export UV_THREADPOOL_SIZE=36 - export NODE_OPTIONS="--max-old-space-size=65536" @@ -35,6 +36,7 @@ phases: # Validate environment variables - echo "Validating environment variables..." - '[ -n "$NEXT_PUBLIC_PORTAL_URL" ] || { echo "❌ NEXT_PUBLIC_PORTAL_URL is not set"; exit 1; }' + - '[ -n "$STATIC_ASSETS_BUCKET" ] || { echo "❌ STATIC_ASSETS_BUCKET is not set"; exit 1; }' # Type check - echo "Type checking..." @@ -45,38 +47,139 @@ phases: - cd apps/$APP_NAME - NODE_TLS_REJECT_UNAUTHORIZED=0 bun run build - # Prepare standalone build + # Upload Next.js chunks and CSS to S3 for CDN performance + - echo "Uploading Next.js static assets to S3..." + - | + if [ -d ".next/static" ]; then + echo "📦 Uploading .next/static/ files to CDN..." + aws s3 sync .next/static/ s3://$STATIC_ASSETS_BUCKET/_next/static/ \ + --cache-control "public, max-age=31536000, immutable" \ + --exclude "*.map" + echo "✅ Uploaded Next.js static assets to S3" + else + echo "⚠️ No .next/static directory found" + fi + + # Upload public assets to S3 for CDN serving + - | + if [ -d "public" ]; then + echo "📦 Uploading public/ files to S3..." + aws s3 sync public/ s3://$STATIC_ASSETS_BUCKET/ \ + --cache-control "public, max-age=86400" \ + --exclude "*.map" \ + --exclude "_next/*" + echo "✅ Uploaded public assets to S3" + else + echo "⚠️ No public directory found" + fi + + # Prepare standalone build for container - echo "Preparing standalone build..." - echo "DEBUG - Checking what Next.js built..." - ls -la .next/ - ls -la .next/standalone/ || echo "No standalone directory" - - echo "DEBUG - Checking if static files exist..." - - ls -la .next/static/ || echo "No static directory found" - - echo "DEBUG - Copying static files to standalone..." - - cp -r public .next/standalone/apps/$APP_NAME/ || echo "No public folder" - - cp -r .next/static .next/standalone/apps/$APP_NAME/.next/ || echo "No .next/static directory" - - echo "DEBUG - Final verification..." - - ls -la .next/standalone/ || echo "Standalone empty" - - find .next/standalone -name "*.css" | head -3 || echo "No CSS files found" - - find .next/standalone -name "*.js" | head -3 || echo "No JS files found" - - # Copy Prisma client - - echo "Copying Prisma client..." - - mkdir -p .next/standalone/node_modules/.prisma .next/standalone/node_modules/@prisma - - if [ -d "../../node_modules/.prisma/client" ]; then - cp -r ../../node_modules/.prisma/client .next/standalone/node_modules/.prisma/; + - echo "DEBUG - Checking standalone structure..." + - find .next/standalone -name "server.js" -ls || echo "No server.js found" + - ls -la .next/standalone/apps/$APP_NAME/ || echo "No app directory" + - echo "DEBUG - Checking app's own .next build structure..." + - ls -la .next/server/ || echo "No .next/server directory" + - ls -la .next/standalone/ || echo "No .next/standalone directory" + - echo "DEBUG - Checking for server.js in various locations..." + - test -f .next/standalone/server.js && echo "✅ Standalone server.js exists" || echo "❌ No standalone server.js" + - find .next -name "server.js" | head -5 || echo "No server.js found anywhere" + + # Create container build directory + - mkdir -p container-build + + # Use the standalone build properly: copy from .next/standalone + app's own build + - echo "DEBUG - Building container from standalone + app build..." + + # Copy the app's own built files (server.js, etc.) + - echo "Copying app's own .next build..." + - cp -r .next container-build/ || echo "App .next copy failed" + + # Copy shared node_modules from standalone (minimal runtime deps) + - echo "Copying standalone node_modules (runtime dependencies)..." + - cp -r .next/standalone/node_modules container-build/ || echo "Standalone node_modules copy failed" + + # Copy or create server.js for standalone + - | + if [ -f ".next/standalone/apps/$APP_NAME/server.js" ]; then + echo "Using app-specific standalone server.js..." + cp .next/standalone/apps/$APP_NAME/server.js container-build/ + elif [ -f ".next/standalone/server.js" ]; then + echo "Using global standalone server.js..." + cp .next/standalone/server.js container-build/ + else + echo "Creating minimal standalone server.js..." + cat > container-build/server.js << 'EOF' + const { createServer } = require('http') + const next = require('next') + + const dev = false + const hostname = process.env.HOSTNAME || '0.0.0.0' + const port = process.env.PORT || 3000 + + // Use the current directory as the Next.js app + const app = next({ dev, hostname, port, dir: __dirname }) + const handle = app.getRequestHandler() + + app.prepare().then(() => { + createServer(async (req, res) => { + await handle(req, res) + }).listen(port, hostname, () => { + console.log(`> Ready on http://${hostname}:${port}`) + }) + }) + EOF + fi + + # Verify the app-specific standalone build + - echo "DEBUG - Verifying app-specific standalone build..." + - ls -la container-build/ + - test -f container-build/server.js && echo "✅ App server.js exists" || echo "❌ App server.js missing" + - test -d container-build/.next && echo "✅ .next directory exists" || echo "❌ .next directory missing" + - test -d container-build/node_modules && echo "✅ node_modules exists" || echo "❌ node_modules missing" + + # Add Dockerfile to the standalone build + - cp Dockerfile container-build/ || echo "No Dockerfile" + + # Ensure Prisma client is available (override standalone if needed) + - echo "Ensuring Prisma client is available..." + - mkdir -p container-build/node_modules/.prisma container-build/node_modules/@prisma + - | + if [ -d "../../node_modules/.prisma/client" ]; then + echo "Copying Prisma client from monorepo root..." + cp -r ../../node_modules/.prisma/client container-build/node_modules/.prisma/ elif [ -d "node_modules/.prisma/client" ]; then - cp -r node_modules/.prisma/client .next/standalone/node_modules/.prisma/; + echo "Copying Prisma client from app directory..." + cp -r node_modules/.prisma/client container-build/node_modules/.prisma/ + else + echo "Warning: No Prisma client found" fi - - if [ -d "../../node_modules/@prisma/client" ]; then - cp -r "../../node_modules/@prisma/client" ".next/standalone/node_modules/@prisma/"; + - | + if [ -d "../../node_modules/@prisma/client" ]; then + echo "Copying @prisma/client from monorepo root..." + cp -r "../../node_modules/@prisma/client" "container-build/node_modules/@prisma/" elif [ -d "node_modules/@prisma/client" ]; then - cp -r "node_modules/@prisma/client" ".next/standalone/node_modules/@prisma/"; + echo "Copying @prisma/client from app directory..." + cp -r "node_modules/@prisma/client" "container-build/node_modules/@prisma/" + else + echo "Warning: No @prisma/client found" fi - # Build Docker image + # Debug: Verify container build contents + - echo "DEBUG - Verifying app-specific container build..." + - ls -la container-build/ + - ls -la container-build/.next/ || echo "❌ .next directory not found" + - ls -la container-build/node_modules/next/ || echo "❌ Next.js module not found" + - ls -la container-build/node_modules/.prisma/ || echo "❌ Prisma client not found" + - test -f container-build/server.js && echo "✅ App-specific server.js found" || echo "❌ App-specific server.js not found" + - head -10 container-build/server.js || echo "❌ Cannot read server.js" + + # Build Docker image (static assets now served from S3) - echo "Building Docker image..." - - docker build --build-arg BUILDKIT_INLINE_CACHE=1 -f ${DOCKERFILE_PATH:-Dockerfile} -t $ECR_REPOSITORY_URI:$IMAGE_TAG . + - docker build --build-arg BUILDKIT_INLINE_CACHE=1 -f container-build/Dockerfile -t $ECR_REPOSITORY_URI:$IMAGE_TAG container-build/ - docker tag $ECR_REPOSITORY_URI:$IMAGE_TAG $ECR_REPOSITORY_URI:latest post_build: diff --git a/apps/app/next.config.ts b/apps/app/next.config.ts index fb956adba..57fae7015 100644 --- a/apps/app/next.config.ts +++ b/apps/app/next.config.ts @@ -1,7 +1,10 @@ import type { NextConfig } from 'next'; +import path from 'path'; import './src/env.mjs'; const config: NextConfig = { + // Use S3 bucket for static assets + assetPrefix: process.env.NODE_ENV === 'production' ? process.env.STATIC_ASSETS_URL : '', poweredByHeader: false, reactStrictMode: true, transpilePackages: ['@trycompai/db'], @@ -29,6 +32,7 @@ const config: NextConfig = { }, authInterrupts: true, }, + outputFileTracingRoot: path.join(__dirname, '../../'), outputFileTracingIncludes: { '/api/**/*': ['./node_modules/.prisma/client/**/*'], }, @@ -99,8 +103,7 @@ const config: NextConfig = { skipTrailingSlashRedirect: true, }; -if (process.env.VERCEL !== '1') { - config.output = 'standalone'; -} +// Always use standalone output +config.output = 'standalone'; export default config; diff --git a/apps/portal/Dockerfile b/apps/portal/Dockerfile index 02f8e2c1b..07e39e762 100644 --- a/apps/portal/Dockerfile +++ b/apps/portal/Dockerfile @@ -13,8 +13,8 @@ ENV PORT=3001 ENV HOSTNAME="0.0.0.0" ENV NODE_TLS_REJECT_UNAUTHORIZED=0 -# Copy the complete standalone build from CodeBuild -COPY .next/standalone ./ +# Copy the standalone build files (already prepared in container-build/) +COPY . ./ # Create non-root user for security RUN addgroup -g 1001 -S nodejs && \ @@ -25,5 +25,5 @@ USER nextjs EXPOSE 3001 -# Use node to run the standalone server - handle monorepo structure -CMD ["sh", "-c", "if [ -f apps/portal/server.js ]; then node apps/portal/server.js; elif [ -f server.js ]; then node server.js; elif [ -f index.js ]; then node index.js; else echo 'No entry point found' && exit 1; fi"] \ No newline at end of file +# Use node to run the standalone server +CMD ["node", "server.js"] \ No newline at end of file diff --git a/apps/portal/buildspec.yml b/apps/portal/buildspec.yml index c4587acf2..63c47dc8a 100644 --- a/apps/portal/buildspec.yml +++ b/apps/portal/buildspec.yml @@ -32,6 +32,10 @@ phases: - echo "Generating Prisma client..." - cd packages/db && bun x prisma generate && cd ../../ + # Validate environment variables + - echo "Validating environment variables..." + - '[ -n "$STATIC_ASSETS_BUCKET" ] || { echo "❌ STATIC_ASSETS_BUCKET is not set"; exit 1; }' + # Type check (optional - skip if script doesn't exist) - echo "Type checking..." - cd apps/$APP_NAME && (bun run typecheck || echo "No typecheck script found, skipping...") && cd ../../ @@ -41,38 +45,135 @@ phases: - cd apps/$APP_NAME - NODE_TLS_REJECT_UNAUTHORIZED=0 bun run build - # Prepare standalone build + # Upload Next.js chunks and CSS to S3 for CDN performance + - echo "Uploading Next.js static assets to S3..." + - | + if [ -d ".next/static" ]; then + echo "📦 Uploading .next/static/ files to CDN..." + aws s3 sync .next/static/ s3://$STATIC_ASSETS_BUCKET/_next/static/ \ + --cache-control "public, max-age=31536000, immutable" \ + --exclude "*.map" + echo "✅ Uploaded Next.js static assets to S3" + else + echo "⚠️ No .next/static directory found" + fi + + # Upload public assets to S3 for CDN serving + - | + if [ -d "public" ]; then + echo "📦 Uploading public/ files to S3..." + aws s3 sync public/ s3://$STATIC_ASSETS_BUCKET/ \ + --cache-control "public, max-age=86400" \ + --exclude "*.map" \ + --exclude "_next/*" + echo "✅ Uploaded public assets to S3" + else + echo "⚠️ No public directory found" + fi + + # Prepare standalone build for container - echo "Preparing standalone build..." - echo "DEBUG - Checking what Next.js built..." - ls -la .next/ - ls -la .next/standalone/ || echo "No standalone directory" - - echo "DEBUG - Checking if static files exist..." - - ls -la .next/static/ || echo "No static directory found" - - echo "DEBUG - Copying static files to standalone..." - - cp -r public .next/standalone/apps/$APP_NAME/ || echo "No public folder" - - cp -r .next/static .next/standalone/apps/$APP_NAME/.next/ || echo "No .next/static directory" - - echo "DEBUG - Final verification..." - - ls -la .next/standalone/ || echo "Standalone empty" - - find .next/standalone -name "*.css" | head -3 || echo "No CSS files found" - - find .next/standalone -name "*.js" | head -3 || echo "No JS files found" - - # Copy Prisma client - - echo "Copying Prisma client..." - - mkdir -p .next/standalone/node_modules/.prisma .next/standalone/node_modules/@prisma - - if [ -d "../../node_modules/.prisma/client" ]; then - cp -r ../../node_modules/.prisma/client .next/standalone/node_modules/.prisma/; + - echo "DEBUG - Checking app's own .next build structure..." + - ls -la .next/server/ || echo "No .next/server directory" + - echo "DEBUG - Checking for server.js in various locations..." + - test -f .next/standalone/server.js && echo "✅ Standalone server.js exists" || echo "❌ No standalone server.js" + - find .next -name "server.js" | head -5 || echo "No server.js found anywhere" + + # Create container build directory + - mkdir -p container-build + + # Use the standalone build properly: copy from .next/standalone + app's own build + - echo "DEBUG - Building container from standalone + app build..." + + # Copy the app's own built files (server.js, etc.) + - echo "Copying app's own .next build..." + - cp -r .next container-build/ || echo "App .next copy failed" + + # Copy shared node_modules from standalone (minimal runtime deps) + - echo "Copying standalone node_modules (runtime dependencies)..." + - cp -r .next/standalone/node_modules container-build/ || echo "Standalone node_modules copy failed" + + # Copy or create server.js for standalone + - | + if [ -f ".next/standalone/apps/$APP_NAME/server.js" ]; then + echo "Using app-specific standalone server.js..." + cp .next/standalone/apps/$APP_NAME/server.js container-build/ + elif [ -f ".next/standalone/server.js" ]; then + echo "Using global standalone server.js..." + cp .next/standalone/server.js container-build/ + else + echo "Creating minimal standalone server.js..." + cat > container-build/server.js << 'EOF' + const { createServer } = require('http') + const next = require('next') + + const dev = false + const hostname = process.env.HOSTNAME || '0.0.0.0' + const port = process.env.PORT || 3000 + + // Use the current directory as the Next.js app + const app = next({ dev, hostname, port, dir: __dirname }) + const handle = app.getRequestHandler() + + app.prepare().then(() => { + createServer(async (req, res) => { + await handle(req, res) + }).listen(port, hostname, () => { + console.log(`> Ready on http://${hostname}:${port}`) + }) + }) + EOF + fi + + # Verify the standalone build + - echo "DEBUG - Verifying standalone build..." + - ls -la container-build/ + - test -f container-build/server.js && echo "✅ Server.js exists" || echo "❌ Server.js missing" + - test -d container-build/.next && echo "✅ .next directory exists" || echo "❌ .next directory missing" + - test -d container-build/node_modules && echo "✅ node_modules exists" || echo "❌ node_modules missing" + + # Add Dockerfile to the standalone build + - cp Dockerfile container-build/ || echo "No Dockerfile" + + # Ensure Prisma client is available (override standalone if needed) + - echo "Ensuring Prisma client is available..." + - mkdir -p container-build/node_modules/.prisma container-build/node_modules/@prisma + - | + if [ -d "../../node_modules/.prisma/client" ]; then + echo "Copying Prisma client from monorepo root..." + cp -r ../../node_modules/.prisma/client container-build/node_modules/.prisma/ elif [ -d "node_modules/.prisma/client" ]; then - cp -r node_modules/.prisma/client .next/standalone/node_modules/.prisma/; + echo "Copying Prisma client from app directory..." + cp -r node_modules/.prisma/client container-build/node_modules/.prisma/ + else + echo "Warning: No Prisma client found" fi - - if [ -d "../../node_modules/@prisma/client" ]; then - cp -r "../../node_modules/@prisma/client" ".next/standalone/node_modules/@prisma/"; + - | + if [ -d "../../node_modules/@prisma/client" ]; then + echo "Copying @prisma/client from monorepo root..." + cp -r "../../node_modules/@prisma/client" "container-build/node_modules/@prisma/" elif [ -d "node_modules/@prisma/client" ]; then - cp -r "node_modules/@prisma/client" ".next/standalone/node_modules/@prisma/"; + echo "Copying @prisma/client from app directory..." + cp -r "node_modules/@prisma/client" "container-build/node_modules/@prisma/" + else + echo "Warning: No @prisma/client found" fi - # Build Docker image + # Debug: Verify container build contents + - echo "DEBUG - Verifying app-specific container build..." + - ls -la container-build/ + - ls -la container-build/.next/ || echo "❌ .next directory not found" + - ls -la container-build/node_modules/next/ || echo "❌ Next.js module not found" + - ls -la container-build/node_modules/.prisma/ || echo "❌ Prisma client not found" + - test -f container-build/server.js && echo "✅ App-specific server.js found" || echo "❌ App-specific server.js not found" + - head -10 container-build/server.js || echo "❌ Cannot read server.js" + + # Build Docker image (static assets now served from S3) - echo "Building Docker image..." - - docker build --build-arg BUILDKIT_INLINE_CACHE=1 -f ${DOCKERFILE_PATH:-Dockerfile} -t $ECR_REPOSITORY_URI:$IMAGE_TAG . + - docker build --build-arg BUILDKIT_INLINE_CACHE=1 -f container-build/Dockerfile -t $ECR_REPOSITORY_URI:$IMAGE_TAG container-build/ - docker tag $ECR_REPOSITORY_URI:$IMAGE_TAG $ECR_REPOSITORY_URI:latest post_build: diff --git a/apps/portal/next.config.ts b/apps/portal/next.config.ts index 34fee5b04..cc5d394c1 100644 --- a/apps/portal/next.config.ts +++ b/apps/portal/next.config.ts @@ -1,8 +1,12 @@ import type { NextConfig } from 'next'; +import path from 'path'; import './src/env.mjs'; const config: NextConfig = { + // Use S3 bucket for static assets + assetPrefix: process.env.NODE_ENV === 'production' ? process.env.STATIC_ASSETS_URL : '', transpilePackages: ['@trycompai/db'], + outputFileTracingRoot: path.join(__dirname, '../../'), outputFileTracingIncludes: { '/api/**/*': ['./prisma/**/*'], }, @@ -33,8 +37,7 @@ const config: NextConfig = { skipTrailingSlashRedirect: true, }; -if (process.env.VERCEL !== '1') { - config.output = 'standalone'; -} +// Always use standalone output +config.output = 'standalone'; export default config;