Deployment
This monorepo is the Twentyfirst Media Group product ecosystem — six deployable web apps plus shared packages. See .design/README.md for brand context and the root README for the full property map.
| App | Domain | Who hosts | Database |
|---|---|---|---|
Chat workspace (apps/chat.twentyfirst.ai) | chat.twentyfirst.ai (or customer domain) | Client (on-prem or private cloud) | DATABASE_URL |
Control plane (apps/twentyfirst.ai) | twentyfirst.ai | Twentyfirst | CONTROL_DATABASE_URL |
Product engineering (apps/twentyfirst.dev) | twentyfirst.dev | Twentyfirst | DATABASE_URL (Drizzle, optional) |
AI marketplace (apps/platform.twentyfirst.ai) | platform.twentyfirst.ai | Twentyfirst | DATABASE_URL (Prisma) |
Corporate media (apps/twentyfirst.media) | twentyfirst.media | Twentyfirst | DATABASE_URL (Drizzle) |
AI academy (apps/twentyfirst.academy) | twentyfirst.academy | Twentyfirst | DATABASE_URL (Drizzle) |
Prerequisites
- Node.js 22.13+ (see
.nvmrc; CI runs on Node 22) - PostgreSQL 18 + pgvector (chat workspace)
- PostgreSQL 18 (control plane, platform, media)
- OpenAI-compatible model endpoint — see model-servers.md (chat workspace)
- Object storage: local (dev) or S3-compatible (production)
- Redis (chat workspace production — TCP rate limits; default
REDIS_URL=redis://127.0.0.1:6379)
Environment files
Copy and configure:
apps/chat.twentyfirst.ai/.env.example→apps/chat.twentyfirst.ai/.envapps/twentyfirst.ai/.env.example→apps/twentyfirst.ai/.envapps/twentyfirst.dev/.env.example→apps/twentyfirst.dev/.envapps/platform.twentyfirst.ai/.env.example→apps/platform.twentyfirst.ai/.envapps/twentyfirst.media/.env.example→apps/twentyfirst.media/.envapps/twentyfirst.academy/.env.example→apps/twentyfirst.academy/.env
CONTROL_PLANE_SIGNING_KEY must match on chat workspace and control plane (license verify + config signing).
twentyfirst.dev needs the AWS SES credentials of twentyfirst.media (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, EMAIL_FROM, LEAD_NOTIFY_EMAIL) so the PHP2Shell website check can email every check to business@ — see php2shell-landing.md. Without them the site works, but no lead emails are sent.
For CI/Docker builds without secrets: SKIP_ENV_VALIDATION=1.
Build
npm ci
npm run lint
npm run build
Run individually:
npm run start -w @repo/chat.twentyfirst.ai # port 3027
npm run start -w @repo/twentyfirst.ai # port 3028
npm run start -w @repo/twentyfirst.dev # port 3029
npm run start -w @repo/platform.twentyfirst.ai # port 3030
npm run start -w @repo/twentyfirst.media # port 3031
npm run start -w @repo/twentyfirst.academy # port 3032
Production server (PM2)
All six apps run on the production server from /var/www/twentyfirst under PM2,
defined in ecosystem.config.cjs (fork mode):
| PM2 name | App folder | Port |
|---|---|---|
chat.twentyfirst.ai | apps/chat.twentyfirst.ai | 3027 |
twentyfirst.ai-control | apps/twentyfirst.ai | 3028 |
dev.twentyfirst.ai | apps/twentyfirst.dev | 3029 |
platform.twentyfirst.ai | apps/platform.twentyfirst.ai | 3030 |
twentyfirst.media-next | apps/twentyfirst.media | 3031 |
academy.twentyfirst.ai | apps/twentyfirst.academy | 3032 |
Each entry runs scripts/start-app.js <app-dir> <port>, which:
- loads
apps/<app>/.env(stays on the server, never committed); aPORTthere wins over the port argument, - resolves Next.js from the app folder (
require.resolve("next/dist/bin/next", { paths: [appDir] })) and logs the version, e.g.[start-app] twentyfirst.media: next@16.4.3 …, - runs
next startand forwards SIGINT/SIGTERM.
All apps share one Next.js version in the root node_modules; CI fails if a nested copy appears.
Deploy
scripts/deploy.sh <app|all> # e.g. scripts/deploy.sh platform.twentyfirst.ai
Runs git pull --ff-only, npm ci (install scripts enabled, dev dependencies included), then per app
prisma generate (+ prisma migrate deploy if the app has Prisma migrations), the production build and — only if all
of that succeeded — pm2 restart <name> --update-env. A failed app keeps running its previous build and the script
exits non-zero. No pkill/pgrep.
Build-time public values set by the deploy script (Next.js inlines NEXT_PUBLIC_* at build time):
| App | Always set | Default if not in the app's .env |
|---|---|---|
| platform | NEXT_PUBLIC_DOMAIN=platform.twentyfirst.ai, NEXT_PUBLIC_COMING_SOON=false | – |
| chat | – | NEXT_PUBLIC_APP_URL=https://chat.twentyfirst.ai (optional NEXT_PUBLIC_BASE_DOMAIN from .env) |
| control | – | NEXT_PUBLIC_CONTROL_URL=https://twentyfirst.ai |
| dev | – | NEXT_PUBLIC_APP_URL=https://dev.twentyfirst.ai |
| academy | – | NEXT_PUBLIC_APP_URL=https://academy.twentyfirst.ai |
| media | – | – (tracking IDs such as NEXT_PUBLIC_GA4_MEASUREMENT_ID, NEXT_PUBLIC_GOOGLE_ADS_*, NEXT_PUBLIC_MICROSOFT_CLARITY_ID, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, NEXT_PUBLIC_EMERGENCY_PHONE come from its .env) |
NODE_ENV=production is always set for builds. npm ci needs access to cdn.sheetjs.com (official source of xlsx).
All apps send mail only through the Amazon SES API (AWS SDK v3): credentials AWS_ACCESS_KEY_ID /
AWS_SECRET_ACCESS_KEY, region AWS_SES_REGION (falls back to AWS_REGION, default eu-central-1),
sender EMAIL_FROM. There is no SMTP transport. The platform scam checker still reads its inbox via IMAP.
Database
Chat workspace
npm run db:push -w @repo/chat.twentyfirst.ai
npm run db:seed -w @repo/chat.twentyfirst.ai
Optional RLS: psql $DATABASE_URL -f apps/chat.twentyfirst.ai/drizzle/rls.sql
Control plane
npm run db:control:push
npm run db:seed -w @repo/twentyfirst.ai
AI marketplace (Prisma)
npm run db:platform:migrate
Corporate media (Drizzle)
npm run db:media:push
npm run db:media:seed
AI academy (Drizzle)
npm run db:academy:push
npm run db:academy:seed
twentyfirst.dev (Drizzle, optional) — PHP2Shell check results, report emails, double opt-ins and follow-ups. Without DATABASE_URL the website check still works; report emails and follow-ups are off.
npm run db:dev:push
Optional: sync Stripe test products from seeded plans — npm run stripe:sync -w @repo/twentyfirst.academy (requires Stripe keys in .env).
Docker
docker compose up --build
Local databases only (no app containers for platform/media yet):
docker compose up -d workspace-db control-db platform-db media-db academy-db
See docker-compose.yml for Postgres services and optional chat/control app images.
Chat workspace (client install)
- Deploy
apps/chat.twentyfirst.aiin the client VPC (or on customer infrastructure). - Pair to twentyfirst.ai with a one-time
INSTANCE_PAIRING_TOKEN, or use air-gapped license + config import. - Point
MODEL_BASE_URLat the client's model endpoint (on-prem GPU, private cloud, etc.). - Point
REDIS_URLat the instance Redis (defaultredis://127.0.0.1:6379for a local daemon). No Upstash REST URL or token is required. - Schedule
POST /api/cron/sync(BearerCRON_SECRET) for config pull and usage push.
Health: GET /api/health
Control plane (Twentyfirst)
- Deploy
apps/twentyfirst.aion your infrastructure. - Restrict
/opsto operator networks or SSO. - Provision customers and instances via the ops UI.
- Instance API (
/api/v1/*) is machine-facing — protect with instance API keys after pairing.
AI marketplace (platform.twentyfirst.ai)
- Deploy
apps/platform.twentyfirst.aion Twentyfirst infrastructure. - Run Prisma migrations:
npm run db:platform:migrate. - Configure Stripe, Replicate, OpenAI, and email (see app
.env.example). - Run the email scam checker worker separately if needed:
npm run email:worker -w @repo/platform.twentyfirst.ai.
Corporate media (twentyfirst.media)
- Deploy
apps/twentyfirst.mediaon Twentyfirst infrastructure. - Push schema:
npm run db:media:push(or migrate in production). - Configure S3/SES, Stripe, and OAuth as needed (see app
.env.example). - Restrict
/adminto trusted networks or SSO.
AI academy (twentyfirst.academy)
- Deploy
apps/twentyfirst.academyon Twentyfirst infrastructure. - Push schema:
npm run db:academy:push(or migrate in production). - Configure
NEXTAUTH_*, Stripe (STRIPE_MODE, test/live keys, webhook secret), and optional S3 for lesson assets (see app.env.example). - Register Stripe webhook endpoint for
checkout.session.completedand related subscription events. - Restrict
/adminto trusted networks or SSO.
Verification
npm run smoke -w @repo/chat.twentyfirst.ai
npm run smoke -w @repo/twentyfirst.ai
Rollback
Redeploy the previous image/tag per app. If a migration is incompatible, restore the database from a pre-migrate snapshot before rolling back.