Skip to content

Commit 93b2693

Browse files
committed
feat: optimize production deployment with enterprise monitoring and logging
- Enhanced GitHub Actions with caching, health checks, and auto-rollback - Upgraded PM2 config with clustering, structured logging, and auto-restart - Optimized Next.js with code splitting (React, TipTap, Supabase, Y.js chunks) - Added comprehensive health endpoint with DB/WS monitoring - Implemented request tracking middleware with performance metrics - Created production monitoring script with auto-recovery - Added logrotate configuration for automated log management - Updated Makefile with safe deployment commands and system checks
1 parent bc7b17e commit 93b2693

10 files changed

Lines changed: 778 additions & 30 deletions

File tree

.github/workflows/prod.docs.plus.yml

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,26 +38,73 @@ jobs:
3838
# This step checks out your repository under $GITHUB_WORKSPACE so your job can access it.
3939
- name: Check out code
4040
uses: actions/checkout@v4
41+
with:
42+
# Only fetch the latest commit to minimize checkout time
43+
fetch-depth: 1
44+
# Preserve existing files for faster deploys
45+
clean: false
46+
47+
# Cache node_modules for faster installs
48+
- name: Cache node modules
49+
uses: actions/cache@v4
50+
with:
51+
path: |
52+
~/.yarn/cache
53+
**/node_modules
54+
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
55+
restore-keys: |
56+
${{ runner.os }}-yarn-
57+
4158
# This step sets up Node.js on the runner and installs the version specified in the matrix above.
4259
- name: Set up Node.js
4360
uses: actions/setup-node@v4
4461
with:
4562
node-version: ${{ matrix.node-version }}
46-
# This step installs project dependencies using Yarn.
47-
# The --frozen-lockfile option ensures the exact package versions specified in yarn.lock are installed.
63+
cache: 'yarn'
64+
65+
# Install dependencies with optimizations
4866
- name: Install dependencies
49-
run: yarn install --frozen-lockfile
67+
run: |
68+
# Use yarn with production optimizations
69+
yarn install --frozen-lockfile --production=false --cache-folder ~/.yarn/cache
70+
# Clean up dev dependencies in production
71+
if [ "$NODE_ENV" = "production" ]; then
72+
yarn install --frozen-lockfile --production=true --cache-folder ~/.yarn/cache
73+
fi
74+
5075
# This step copies the .env file from the root directory to the required directories for each package.
5176
# Update these paths if your repository structure is different.
5277
- name: Copy .env files
5378
run: |
5479
cp ../../../.env packages/webapp
5580
cp ../../../.env packages/hocuspocus.server
81+
82+
# Pre-build health check
83+
- name: Pre-build health check
84+
run: |
85+
# Check if essential services are running
86+
make supabase_status || echo "Supabase not running, continuing..."
87+
5688
- name: Build monorepo packages
57-
run: yarn build
89+
run: |
90+
# Build with performance optimizations
91+
NODE_ENV=production yarn build
92+
93+
# Post-build validation
94+
- name: Validate build artifacts
95+
run: |
96+
# Check if critical build outputs exist
97+
if [ -d "packages/webapp/.next" ]; then
98+
echo "✓ Next.js build artifacts found"
99+
ls -la packages/webapp/.next/
100+
else
101+
echo "✗ Next.js build failed - no .next directory"
102+
exit 1
103+
fi
58104
env:
59105
# The environment variable DATABASE_URL is sourced from a secret in your repository.
60106
DATABASE_URL: ${{secrets.STAGE_DATABASE_URL}}
107+
NODE_ENV: production
61108

62109
# The "build-front" job builds the front-end, it depends on the "setup" job.
63110
build-front:
@@ -67,9 +114,61 @@ jobs:
67114
# This job will only run if the commit message contains the word 'front'.
68115
if: contains(github.event.head_commit.message, 'front')
69116
steps:
117+
# Pre-deployment health check
118+
- name: Pre-deployment health check
119+
run: |
120+
# Check current PM2 status
121+
pm2 list || echo "PM2 not running, will start fresh"
122+
# Check disk space
123+
df -h
124+
# Check memory usage
125+
free -h
126+
127+
# Backup current deployment for rollback
128+
- name: Backup current deployment
129+
run: |
130+
# Create backup of current .next build if it exists
131+
if [ -d "packages/webapp/.next" ]; then
132+
mv packages/webapp/.next packages/webapp/.next.backup.$(date +%Y%m%d-%H%M%S)
133+
echo "✓ Backup created"
134+
fi
135+
70136
# This step runs the build command for the front-end.
71-
- name: Build Front-end
72-
run: make build_front_production
137+
- name: Build and Deploy Front-end
138+
run: |
139+
# Build with enhanced error handling
140+
if ! make build_front_production; then
141+
echo "✗ Build failed, attempting rollback..."
142+
# Restore backup if build fails
143+
if [ -d "packages/webapp/.next.backup.*" ]; then
144+
LATEST_BACKUP=$(ls -t packages/webapp/.next.backup.* | head -n1)
145+
mv "$LATEST_BACKUP" packages/webapp/.next
146+
echo "✓ Rollback completed"
147+
fi
148+
exit 1
149+
fi
150+
151+
# Post-deployment health check
152+
- name: Post-deployment health check
153+
run: |
154+
# Wait for application to start
155+
sleep 10
156+
157+
# Check if the application is responding
158+
if curl -f http://localhost:3001/api/health; then
159+
echo "✓ Application health check passed"
160+
else
161+
echo "✗ Application health check failed"
162+
# Show PM2 logs for debugging
163+
pm2 logs nextjs_production --lines 50
164+
exit 1
165+
fi
166+
167+
# Cleanup old backups (keep last 3)
168+
- name: Cleanup old backups
169+
run: |
170+
# Remove old backup directories (keep last 3)
171+
ls -t packages/webapp/.next.backup.* 2>/dev/null | tail -n +4 | xargs rm -rf || true
73172
74173
# The "build-back" job builds the back-end, it also depends on the "setup" job.
75174
build-back:

Makefile

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,37 @@ build:
8282
fastRun:
8383
docker-compose -f docker-compose.prod.yml up
8484

85-
# Build and run frontend in stage environment
85+
# Build and run frontend in stage environment with monitoring
8686
build_front_stage:
87-
cd packages/webapp && npm run build && npm run pm2:start:stage
88-
89-
# Build and run frontend in production environment
87+
@echo "🏗️ Building and deploying to stage environment..."
88+
@cd packages/webapp && \
89+
NODE_ENV=production npm run build && \
90+
npm run pm2:start:stage && \
91+
sleep 10 && \
92+
curl -f http://localhost:3000/api/health || (echo "❌ Stage health check failed" && pm2 logs nextjs_stage --lines 20 && exit 1) && \
93+
echo "✅ Stage deployment completed!"
94+
95+
# Build and run frontend in production environment with optimization
9096
build_front_production:
91-
cd packages/webapp && npm run build && npm run pm2:start:prod
97+
@echo "🚀 Starting production build and deployment..."
98+
@echo "📊 Pre-build system check..."
99+
@cd packages/webapp && \
100+
echo "Memory usage before build:" && free -h && \
101+
echo "Disk space:" && df -h . && \
102+
echo "🏗️ Building Next.js application..." && \
103+
NODE_ENV=production npm run build && \
104+
echo "✅ Build completed successfully" && \
105+
echo "📈 Build size analysis:" && \
106+
du -sh .next/ && \
107+
echo "🔄 Starting PM2 deployment..." && \
108+
npm run pm2:start:prod && \
109+
echo "⏳ Waiting for application startup..." && \
110+
sleep 15 && \
111+
echo "🩺 Running health check..." && \
112+
curl -f http://localhost:3001/api/health || (echo "❌ Health check failed" && pm2 logs nextjs_production --lines 20 && exit 1) && \
113+
echo "✅ Production deployment completed successfully!" && \
114+
echo "📊 Final PM2 status:" && \
115+
npm run pm2:status
92116

93117
# Build, stop and remove the existing stage container, and run a new stage container
94118
build_hocuspocus.server_stage: down_stage
@@ -134,3 +158,58 @@ help: # Show available commands
134158

135159
generate_supabase_types: # Generate Supabase TypeScript types
136160
cd packages/supabase && yarn supabase:types
161+
162+
# Frontend monitoring and troubleshooting commands
163+
pm2_status: # Show PM2 process status
164+
cd packages/webapp && npm run pm2:status
165+
166+
pm2_logs: # Show PM2 logs for production
167+
cd packages/webapp && npm run pm2:logs:prod
168+
169+
pm2_logs_error: # Show PM2 error logs only
170+
cd packages/webapp && npm run pm2:logs:error
171+
172+
pm2_restart: # Restart production frontend
173+
cd packages/webapp && npm run pm2:restart
174+
175+
pm2_reload: # Graceful reload production frontend
176+
cd packages/webapp && npm run pm2:reload
177+
178+
pm2_monitor: # Open PM2 monitoring dashboard
179+
cd packages/webapp && npm run pm2:monitor
180+
181+
health_check: # Manual health check
182+
@echo "🩺 Running health check..."
183+
@curl -s http://localhost:3001/api/health | jq '.' || echo "❌ Health check failed or jq not installed"
184+
185+
system_info: # Show system information
186+
@echo "📊 System Information:"
187+
@echo "Memory usage:" && free -h
188+
@echo "Disk usage:" && df -h
189+
@echo "CPU usage:" && top -bn1 | grep "Cpu(s)"
190+
@echo "PM2 processes:" && pm2 list
191+
192+
cleanup_logs: # Clean up old log files
193+
cd packages/webapp && npm run logs:cleanup && echo "✅ Log cleanup completed"
194+
195+
# Production deployment with rollback capability
196+
deploy_production_safe: # Safe production deployment with auto-rollback
197+
@echo "🚀 Starting safe production deployment..."
198+
@cd packages/webapp && \
199+
if [ -d ".next" ]; then \
200+
echo "📦 Creating backup..." && \
201+
cp -r .next .next.backup.$$(date +%Y%m%d-%H%M%S); \
202+
fi && \
203+
if make build_front_production; then \
204+
echo "✅ Deployment successful!"; \
205+
else \
206+
echo "❌ Deployment failed, attempting rollback..." && \
207+
if [ -d ".next.backup.*" ]; then \
208+
LATEST_BACKUP=$$(ls -t .next.backup.* | head -n1) && \
209+
rm -rf .next && \
210+
mv $$LATEST_BACKUP .next && \
211+
npm run pm2:restart && \
212+
echo "🔄 Rollback completed"; \
213+
fi && \
214+
exit 1; \
215+
fi

packages/webapp/.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ npm-debug.log*
2424
yarn-debug.log*
2525
yarn-error.log*
2626

27+
# PM2 and application logs
28+
/logs/*.log
29+
/logs/monitor.log
30+
.next.backup.*
31+
32+
# PM2 ecosystem file logs
33+
pm2.log
34+
ecosystem.config.js.log
35+
2736
# local env files
2837
.env*.local
2938
.env*.production

packages/webapp/logrotate.conf

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Log rotation configuration for docsy webapp
2+
# Place this file in /etc/logrotate.d/ or configure manually
3+
4+
/Users/macbook/workspace/docsy/packages/webapp/logs/*.log {
5+
daily
6+
missingok
7+
rotate 30
8+
compress
9+
delaycompress
10+
notifempty
11+
sharedscripts
12+
postrotate
13+
# Signal PM2 to reopen log files
14+
pm2 reloadLogs > /dev/null 2>&1 || true
15+
endscript
16+
# Set appropriate ownership and permissions
17+
create 0644 $(whoami) $(whoami)
18+
# Maximum log file size before rotation
19+
size 100M
20+
# Don't rotate if log file is empty
21+
notifempty
22+
# Copy then truncate instead of moving (better for PM2)
23+
copytruncate
24+
}

packages/webapp/middleware.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { NextResponse, type NextRequest } from 'next/server'
22

33
export async function middleware(request: NextRequest) {
4+
const startTime = Date.now()
5+
const requestId = crypto.randomUUID()
6+
47
// Check for error-related query parameters
58
const searchParams = request.nextUrl.searchParams
69
const hasError = searchParams.has('error')
@@ -22,6 +25,33 @@ export async function middleware(request: NextRequest) {
2225
}
2326
})
2427

28+
// Add request tracking headers
29+
response.headers.set('X-Request-ID', requestId)
30+
response.headers.set('X-Response-Time', `${Date.now() - startTime}ms`)
31+
32+
// Log request in production (simplified middleware logging)
33+
if (process.env.NODE_ENV === 'production') {
34+
const logData = {
35+
requestId,
36+
method: request.method,
37+
url: request.url,
38+
userAgent: request.headers.get('user-agent'),
39+
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown',
40+
timestamp: new Date().toISOString(),
41+
duration: Date.now() - startTime
42+
}
43+
44+
// Log to console (PM2 will capture this)
45+
console.log(
46+
JSON.stringify({
47+
level: 'info',
48+
message: 'Request processed',
49+
type: 'middleware_request',
50+
...logData
51+
})
52+
)
53+
}
54+
2555
return response
2656
}
2757

0 commit comments

Comments
 (0)