This document provides comprehensive deployment instructions for the **Flash Turbo CMS mult---
ecosystem.config.js)module.exports = {
apps: [{
name: 'multi-store',
script: './server.js',
cwd: '/opt/flash-cms/current',
instances: 1,
exec_mode: 'fork',
max_memory_restart: '400M', // Adjust based on instance size
env: {
NODE_ENV: 'production',
PORT: 3000
},
env_production: {
NODE_ENV: 'production'
},
log_file: '/opt/flash-cms/logs/combined.log',
out_file: '/opt/flash-cms/logs/out.log',
error_file: '/opt/flash-cms/logs/error.log',
time: true
}]
};
/etc/nginx/sites-available/flash-cms)# Multi-tenant Next.js + PayloadCMS configuration
server {
listen 80;
server_name multisofts.com flash-cms.online *.multisofts.com *.flash-cms.online;
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
# Serve static files directly
location /_next/static/ {
alias /opt/flash-cms/current/.next/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location /public/ {
alias /opt/flash-cms/current/public/;
expires 30d;
add_header Cache-Control "public";
}
# Proxy all other requests to Next.js
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
deploy.sh)#!/bin/bash
set -e
APP_NAME="flash-cms"
DEPLOY_DIR="/opt/$APP_NAME"
RELEASES_DIR="$DEPLOY_DIR/releases"
CURRENT_LINK="$DEPLOY_DIR/current"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RELEASE_DIR="$RELEASES_DIR/$TIMESTAMP"
echo "π Starting deployment of $APP_NAME..."
# Create directories
mkdir -p $RELEASES_DIR
mkdir -p $DEPLOY_DIR/logs
# Extract new release
echo "π¦ Extracting release artifacts..."
mkdir -p $RELEASE_DIR
tar -xzf /tmp/flash-cms-build.tar.gz -C $RELEASE_DIR
# Install dependencies in standalone mode
echo "π Setting up standalone build..."
cd $RELEASE_DIR
# Update symlink atomically
echo "π Updating current release symlink..."
ln -nfs $RELEASE_DIR $CURRENT_LINK
# Reload PM2 (zero downtime)
echo "π Reloading PM2 processes..."
pm2 reload $APP_NAME --wait-ready
# Cleanup old releases (keep last 5)
echo "π§Ή Cleaning up old releases..."
cd $RELEASES_DIR
ls -1t | tail -n +6 | xargs -d '\n' rm -rf --
echo "β
Deployment completed successfully!"
.github/workflows/deploy-ec2.yml)name: Deploy to EC2
on:
push:
branches: [main]
paths: ['apps/multi-store/**']
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: '1.2.21'
- name: Cache dependencies
uses: actions/cache@v3
with:
path: |
~/.bun/install/cache
node_modules
.turbo
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
- name: Install dependencies
run: bun install
- name: Build application
run: bun run build --filter multi-store
env:
NODE_ENV: production
- name: Create deployment package
run: |
cd apps/multi-store
tar -czf ../../flash-cms-build.tar.gz \
.next/standalone \
.next/static \
public \
package.json
- name: Deploy to EC2
uses: appleboy/scp-action@v0.1.4
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
source: "flash-cms-build.tar.gz,scripts/deploy.sh"
target: "/tmp"
- name: Execute deployment
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
chmod +x /tmp/deploy.sh
sudo /tmp/deploy.sh
ecosystem.config.js β PM2 config for zero-downtime reloads.deploy.sh β Atomic deployment script (symlink strategy).ci-deploy.yml β GitHub Actions workflow (build + deploy).turbo.json β Turborepo pipeline config with caching..env.production.example with required variables. Next.js application** (apps/multi-store) on AWS EC2 in a cost-effective and scalable manner.Application: apps/multi-store v1.2.6
Tech Stack: Next.js 15.5.2 + PayloadCMS 3.55.0 + MongoDB
Build Tool: Turbopack (with Webpack fallback)
Package Manager: Bun 1.2.21
Current Hosting: Vercel (migrating to EC2 for cost optimization)
Q: The app is currently on Vercel, but I want to cut costs. Can I deploy on EC2 with minimal RAM (1GB or even 512MB)?
A: Yes. Builds should happen off-instance (CI or local). You only deploy the build artifacts. Runtime memory is the main limiter, not build.
On 512MB (t4g.nano) it can run with swap enabled, and upgrading to 1GB (t4g.micro) is smoother for production traffic.
Q: Can PM2 handle zero-downtime deploys with new builds?
A: Yes. By using PM2βs reload and a symlink release strategy (current -> releases/<timestamp>), new builds can be deployed without downtime.
Q: Can GitHub Actions build the app and deploy to EC2?
A: Yes. The build happens in CI (GitHub runner with more RAM/CPU). Artifacts are zipped, transferred via SCP, extracted, and deployed with a shell script on EC2. This avoids heavy builds on small instances.
Q: Can I run on t4g.nano (512MB) or should I stay on t4g.micro (1GB)?
A: Start with 512MB for cost savings, but add swap (2GB) to prevent OOM. Upgrade to 1GB when you see memory struggles. Running multiple nanos with a load balancer is more expensive than a single micro.
Q: Vercel handles caching for me. How do I replicate that on EC2?
A: Use Cloudflare as a CDN + SSL terminator. Configure caching rules to cache static assets (/_next/static, /public), bypass API routes, and set short TTL caching for tenant landing pages. Dynamic dashboards/CRM routes remain un-cached.
Q: This is a multi-tenant app. Will the deployment plan support multiple domains (e.g., multisofts.com, flash-cms.online)?
A: Yes. NGINX proxies requests to Node.js and preserves the Host header so Next.js + PayloadCMS multi-tenant plugin can resolve tenants correctly.
apps/multi-storebun run build --filter multi-storenext.config.ts)vm.swappiness to reduce OOM risk.bun, pm2, nginx, node (v22.18.0)./_next/static, /public) directly with long cache TTL.Host header β required for PayloadCMS multi-tenant plugin resolution./admin/*) with proper auth headers./_next/static/* β Cache Everything, TTL = 1 month./public/* β Cache Everything, TTL = 1 month./api/* β Bypass (PayloadCMS API routes)./admin/* β Bypass (Admin interface)./, /about, /shop/*) β Cache short TTL (5m) if static; bypass if dynamic..turbo, .next/cache, node_modules).bun run build --filter multi-store --turbopack.next/standalone, .next/static, public, package.json, environment configs.deploy.sh extracts into timestamped release directory.current symlink β points to new release.reload multi-store β zero downtime switch.multi-storenode server.js).max_memory_restart: 400 (for 512MB instance) or 800 (for 1GB instance)..env.production.pm2-logrotate.fork mode for 512MB, cluster mode possible for 1GB+.t4g.nano.t4g.micro when needed.# Database
MONGODB_URI=mongodb://...
DATABASE_URI=$MONGODB_URI
# PayloadCMS
PAYLOAD_SECRET=your-secret-key
PAYLOAD_PUBLIC_SERVER_URL=https://your-domain.com
# Storage (S3/Vercel Blob)
S3_ENDPOINT=...
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...
S3_BUCKET=...
S3_REGION=...
# Authentication
BETTER_AUTH_SECRET=...
BETTER_AUTH_URL=$PAYLOAD_PUBLIC_SERVER_URL
# AI Features (Optional)
OPENAI_API_KEY=...
# Multi-tenant domains
PAYLOAD_PUBLIC_TENANT_DOMAINS=multisofts.com,flash-cms.online
apps/multi-store/next.config.ts)standalone mode enabledturbo.json)build: { dependsOn: ["^build"], outputs: [".next/**", "!.next/cache/**"] }--filter multi-store for targeted deploymentecosystem.config.js β PM2 config for zero-downtime reloads.deploy.sh β Atomic deployment script (symlink strategy).ci-deploy.yml β GitHub Actions workflow (build + deploy).turbo.json β Turborepo pipeline config with caching..env.production.example with required variables..turbo, .next/cache, and deps.22, 80.pm2-logrotate installed for log management.pm2 monit, external uptime checks, or integrate with CloudWatch.Host header forwarding.This setup is cost-optimized, production-ready, and scalable for your multi-tenant CMS/CRM.