This file serves as the central navigation point for LLMs working on this Payload CMS AWS self-hosting project. All documentation should be maintained and kept up-to-date by LLMs during development.
- ALWAYS commit code changes immediately after making modifications on the server
- ALWAYS update documentation when making changes to the codebase or solving issues
- NEVER skip the commit step - all server changes must be reflected in the repository
- Add new documentation when implementing new features or deployment strategies
- Keep all links in this file current - if you create or rename docs, update the links here
- Use clear, LLM-friendly formatting with explicit examples and context
CRITICAL: Infrastructure secrets and IDs are stored in .deployment-secrets (gitignored)
- File Location:
/.deployment-secrets- Contains all AWS resource IDs, endpoints, and credentials - Usage: Read this file first when performing deployment tasks to get real values
- Update: Keep EC2_PUBLIC_IP current when instance restarts
- Security: File is gitignored - never commit secrets to repository
- Make necessary changes on the server or locally
- Update/create documentation reflecting the changes and lessons learned
- Commit and push changes to the repository immediately
- Update this CLAUDE.md file if documentation structure changes
This repository contains a Payload CMS implementation designed for self-hosting on AWS. Payload CMS is embedded within a Next.js application and they deploy together as a single unit.
Repository: https://github.com/focusreactive/payload-aws-selfhosted
/docs/01-payload-architecture.md- Explains how Payload CMS integrates with Next.js/docs/02-deployment-overview.md- General deployment strategies and Payload Cloud comparison/docs/03-aws-deployment-options.md- Specific AWS deployment methods and configurations
/docs/04-aws-ec2-implementation-plan.md- Original implementation plan with current status update- IMPORTANT: This file contains current AWS resources already deployed (EC2, RDS, S3)
- SSH Access: Instructions for connecting to the EC2 instance are included
- Status: Updated with completion status and current issues
/docs/05-complete-deployment-guide.md- Comprehensive deployment guide- LLM-Ready: Complete step-by-step instructions for automated deployment
- Troubleshooting: All issues encountered and their solutions documented
- Production-Ready: Covers security, SSL configuration, IAM roles, and best practices
- Current Status: β Deployment 100% complete - fully operational
/docs/06-production-quick-start.md- NEW: Quick deployment reference- Quick Reference: Exact commands for production deployment
- Common Fixes: Solutions for frequent issues
- Critical Steps: Highlights must-do steps like static file copying
- Current IPs: Updated with latest EC2 public IP
/nginx-csp-fix.conf- NEW: Nginx CSP configuration for Next.js compatibility- Critical: Contains correct CSP headers that allow Next.js JavaScript execution
- Issue: Prevents blank page issues caused by CSP violations
/docs/environment-setup.md- Environment variables and configuration (covered in guide above)/docs/database-setup.md- PostgreSQL configuration for AWS RDS (covered in guide above)/docs/file-storage-setup.md- S3 configuration for media uploads (covered in guide above)/docs/docker-deployment.md- Container deployment instructions/docs/monitoring-and-logs.md- CloudWatch and monitoring setup
- Framework: Next.js with Payload CMS 3.0
- Database: PostgreSQL (via
@payloadcms/db-postgres) - Container: Docker (Dockerfile already configured)
- Target Platform: AWS
IMPORTANT: The following AWS resources are deployed and FULLY OPERATIONAL:
- EC2 Instance:
i-050cf5824f2b89881(Payload CMS) in eu-north-1- Status: β Running with Node.js 20, PM2, Nginx configured
- Public IP: 13.61.178.211 (
β οΈ Changes after stop/start - check AWS console) - Services: PM2 process manager, Nginx reverse proxy
- Access: LIVE at http://[EC2_PUBLIC_IP]/
- RDS Database:
payload-cms-db(PostgreSQL db.t4g.micro)- Status: β Available with schema created and migrations completed
- Password: Stored securely in AWS Secrets Manager
- SSL: β Configured and operational
- S3 Bucket:
payload-cms-assets-000- Status: β Configured with IAM role access (no access keys needed)
- IAM Resources: PayloadCMSS3Role, PayloadCMSS3Policy, PayloadCMSS3Profile
- Status: β Configured for secure EC2-to-S3 access
CRITICAL: LLMs have direct SSH access to the production server:
First, get the current EC2 public IP:
# Get EC2 public IP (changes on restart)
aws ec2 describe-instances --instance-ids i-050cf5824f2b89881 --region eu-north-1 --query "Reservations[0].Instances[0].PublicIpAddress" --output textSSH Access Details:
- SSH Command:
ssh -i ~/.ssh/"Payload CMS.pem" ubuntu@[EC2_PUBLIC_IP] - Key Location:
~/.ssh/Payload CMS.pem(already available locally) - User:
ubuntu(NOTec2-user) - Instance ID:
i-050cf5824f2b89881 - Permissions: Key has correct 400 permissions
- Verified Working: β Tested and functional
Common SSH Operations (replace [EC2_PUBLIC_IP] with actual IP):
# Basic connection test
ssh -i ~/.ssh/"Payload CMS.pem" ubuntu@[EC2_PUBLIC_IP] "whoami && pwd"
# Check PM2 status
ssh -i ~/.ssh/"Payload CMS.pem" ubuntu@[EC2_PUBLIC_IP] "cd /opt/payload-app && pm2 status"
# View application logs
ssh -i ~/.ssh/"Payload CMS.pem" ubuntu@[EC2_PUBLIC_IP] "cd /opt/payload-app && pm2 logs payload-cms --lines 10"
# Restart application
ssh -i ~/.ssh/"Payload CMS.pem" ubuntu@[EC2_PUBLIC_IP] "cd /opt/payload-app && pm2 restart payload-cms"
# Pull latest code
ssh -i ~/.ssh/"Payload CMS.pem" ubuntu@[EC2_PUBLIC_IP] "cd /opt/payload-app && git pull origin main"π LIVE DEPLOYMENT: Payload CMS successfully deployed and accessible at:
- Homepage: http://[EC2_PUBLIC_IP]/
- Admin Panel: http://[EC2_PUBLIC_IP]/admin
- API: http://[EC2_PUBLIC_IP]/api/*
For detailed AWS resource information and SSH instructions, see /docs/04-aws-ec2-implementation-plan.md
# Development
pnpm dev
# Build for production
pnpm build
# Start production server
pnpm start
# Database migrations
pnpm payload migrate:create
pnpm payload migrateDATABASE_URI- PostgreSQL connection stringPAYLOAD_SECRET- Security secret for PayloadNEXT_PUBLIC_SERVER_URL- Public URL of the applicationBLOB_READ_WRITE_TOKEN- For blob storage (if using Vercel Blob)
CRITICAL: After every action that modifies code or configuration, you MUST:
- β Update documentation - Document all changes, issues encountered, and solutions
- β Commit code changes - Create git commit with descriptive message and push to repository
- β Update CLAUDE.md - Add new documentation links and update status
Failure to follow these steps will result in lost work and deployment inconsistencies.
When working on this project, ensure you:
- β Update relevant documentation after code changes
- β Add new docs for new features or configurations
- β Update this CLAUDE.md file with new documentation links
- β Include examples and context in all documentation
- β Test all commands and configurations before documenting
- β Keep documentation focused on practical implementation
- β IMMEDIATELY commit and push after any code/config changes
- β Document every issue and solution for future LLM reference
/
βββ src/ # Next.js and Payload source code
βββ docs/ # Project documentation (maintain this!)
βββ public/ # Static assets
βββ .deployment-secrets # π LLM infrastructure secrets (gitignored)
βββ .env # Application environment variables
βββ .gitignore # Git ignore rules (includes .deployment-secrets)
βββ Dockerfile # Container configuration
βββ docker-compose.yml # Local development with MongoDB (needs update for PostgreSQL)
βββ CLAUDE.md # This file - central LLM guide
βββ package.json # Dependencies and scripts
Final Status: Payload CMS successfully deployed to AWS - fully functional in production!
β All Components Working:
- β Homepage & Frontend: Complete functionality at http://13.61.178.211/
- β Admin Panel: Fully operational at http://13.61.178.211/admin
- β API Endpoints: All REST/GraphQL APIs working perfectly
- β Database: PostgreSQL RDS with SSL configuration
- β File Storage: S3 integration with IAM roles (no credentials needed)
- β Infrastructure: EC2, RDS, S3, IAM fully deployed and secure
- β Process Management: PM2 with persistence and auto-restart
- β Reverse Proxy: Nginx with security headers and CSP
- β Static Files: Properly served in production standalone build
π Production Readiness:
- Headless CMS: β 100% ready for production use
- Content API: β All endpoints operational
- File Uploads: β S3 storage configured and ready
- Admin Interface: β Fully functional with all features
Critical Lessons Learned for Future LLMs:
- SSL Configuration: Always configure SSL in payload.config.ts, not connection strings
- Security Groups: Ensure outbound internet access for package installation
- IAM Roles: Use instance profiles instead of access keys for S3 access
- PM2 Configuration: Use .cjs extension and load .env file explicitly in production
- Static Files: MUST copy static files after build:
cp -r .next/static .next/standalone/.next/ - EC2 IP Changes: Public IP changes after stop/start (not reboot) - always check current IP
- Build Memory: Use conservative memory limits on small instances:
NODE_OPTIONS='--max-old-space-size=768' - Environment Variables: PM2 production config must explicitly load .env file
# Get current IP (β οΈ IP changes after stop/start)
EC2_IP=$(aws ec2 describe-instances --instance-ids i-050cf5824f2b89881 --region eu-north-1 --query "Reservations[0].Instances[0].PublicIpAddress" --output text)
# SSH to server
ssh -i ~/.ssh/"Payload CMS.pem" ubuntu@$EC2_IP
# Update environment with new IP if needed
cd /opt/payload-app
sed -i "s|NEXT_PUBLIC_SERVER_URL=.*|NEXT_PUBLIC_SERVER_URL=http://$EC2_IP|" .env
# Build production version (with memory limit for small instances)
NODE_OPTIONS='--max-old-space-size=768' pnpm build
# CRITICAL: Copy static files to standalone directory
cp -r .next/static .next/standalone/.next/
cp -r public .next/standalone/
# Start with production PM2 config
pm2 delete all
pm2 start ecosystem.config.production.cjs --name payload-cms
pm2 save
# Verify deployment
pm2 status
curl localhost:3000
curl localhost:3000/adminconst dotenv = require('dotenv');
const path = require('path');
// Load environment variables from parent directory
dotenv.config({ path: path.join(__dirname, '.env') });
module.exports = {
apps: [{
name: 'payload-cms',
script: './server.js',
cwd: '/opt/payload-app/.next/standalone',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
...process.env,
NODE_ENV: 'production',
PORT: 3000,
HOSTNAME: '0.0.0.0'
},
log_file: '/opt/payload-app/logs/combined.log',
out_file: '/opt/payload-app/logs/out.log',
error_file: '/opt/payload-app/logs/error.log',
merge_logs: true,
time: true
}]
}- Static Files: MUST copy after every build - Next.js standalone doesn't include them
- Environment Variables: PM2 config must explicitly load .env file
- Memory Limits: Use conservative limits on t3.medium instances
- IP Changes: EC2 public IP changes after stop/start - update .env accordingly
# Local development continues as normal
pnpm dev # Runs on localhost:3000- Static Files: After
pnpm build, ALWAYS copy:cp -r .next/static .next/standalone/.next/ - PM2 Config: Must explicitly load .env file in production mode
- EC2 IP: Changes after stop/start - always verify current IP
- Memory: Use
NODE_OPTIONS='--max-old-space-size=768'for builds on small instances - SSH: Always use
ubuntuuser, NOTec2-user
- Blank pages/404 JS files: Copy static files to standalone directory
- 502 errors: Check PM2 logs, ensure .env is loaded
- Build failures: Reduce memory allocation, clear .next directory
- Connection timeouts: Check security groups for outbound rules
- Database SSL errors: Configure SSL in payload.config.ts, not connection string
- β Pull latest code:
git pull origin main - β Update .env with current EC2 IP
- β Build with memory limit:
NODE_OPTIONS='--max-old-space-size=768' pnpm build - β Copy static files:
cp -r .next/static .next/standalone/.next/ - β Start PM2:
pm2 start ecosystem.config.production.cjs --name payload-cms - β Verify: Access http://[EC2_IP]/ and /admin
- ALWAYS update docs after solving issues
- ALWAYS commit and push changes immediately
- ALWAYS update CLAUDE.md with new learnings