917 lines
27 KiB
Markdown
917 lines
27 KiB
Markdown
# IGNY8 Phase 0: Production Migration (00C)
|
|
|
|
**Document ID:** 00C-igny8-production-migration
|
|
**Phase:** Phase 0: Production Migration
|
|
**Version:** 2.0
|
|
**Date:** 2026-03-23
|
|
**Status:** In Progress
|
|
|
|
**Related Docs:**
|
|
- 00A: GitHub Repository Consolidation (completed)
|
|
- 00B: New VPS Infrastructure Setup (completed — new VPS provisioned with test subdomains)
|
|
- 00D: Staging Environment Setup (follows this doc)
|
|
- 00E: Old VPS Cleanup & Decommission (follows)
|
|
|
|
---
|
|
|
|
## 1. Current State
|
|
|
|
### 1.1 Running System (Old VPS)
|
|
|
|
**Version:** IGNY8 v1.8.4
|
|
**Runtime:** Docker Compose with production compose file
|
|
**Project Name:** igny8-app
|
|
**Compose File:** docker-compose.app.yml
|
|
|
|
#### Active Containers
|
|
| Container | Service | Port (Internal) | Technology |
|
|
|-----------|---------|----------------|------------|
|
|
| igny8_backend | REST API | 8010 | Django 4.2 + Gunicorn (4 workers, 120s timeout) |
|
|
| igny8_frontend | Web UI | 5173 → 8021 | Vite dev server |
|
|
| igny8_celery_worker | Task worker | N/A | Celery |
|
|
| igny8_celery_beat | Task scheduler | N/A | Celery Beat |
|
|
| igny8_postgres | Database | 5432 | PostgreSQL 16 |
|
|
| igny8_redis | Cache/Broker | 6379 | Redis 7 (DB 0) |
|
|
| caddy | Reverse proxy/SSL | 80, 443 | Caddy 2 |
|
|
| marketing | Render service | 8023 | Custom service |
|
|
| sites | Render service | 8024 | Custom service |
|
|
|
|
#### Database
|
|
- **Database Name:** igny8_db
|
|
- **User:** igny8
|
|
- **Engine:** PostgreSQL 16
|
|
- **Backup Location:** /data/app/igny8/scripts/ops/
|
|
- **Backup Scripts:** backup-db.sh, backup-full.sh
|
|
|
|
#### Storage & Volumes
|
|
| Volume | Mount Path | Content |
|
|
|--------|-----------|---------|
|
|
| Backend Code | /data/app/igny8/backend | Django app source |
|
|
| Shared Data | /data/app/igny8 | Media, plugins, static files |
|
|
| Logs | /data/app/logs | Container logs |
|
|
| Plugin ZIPs | /plugins/wordpress/source/igny8-wp-bridge/ | Distribution files |
|
|
|
|
#### Environment Configuration
|
|
**Source:** .env file on old VPS or Docker secrets
|
|
**Key Variables:**
|
|
- DATABASE_URL or (DB_HOST, DB_NAME, DB_USER, DB_PASSWORD)
|
|
- REDIS_HOST, REDIS_PORT, REDIS_DB
|
|
- SECRET_KEY, JWT_SECRET_KEY
|
|
- CORS_ALLOWED_ORIGINS
|
|
- CELERY_BROKER_URL
|
|
- Django DEBUG, ALLOWED_HOSTS
|
|
|
|
**Important:** AI integration keys stored in database (GlobalIntegrationSettings table), NOT in env vars.
|
|
|
|
#### Networking
|
|
- **Primary Domain:** app.igny8.com (frontend)
|
|
- **API Domain:** api.igny8.com (backend)
|
|
- **Marketing:** igny8.com
|
|
- **DNS Provider:** Current registrar (NOT yet on Cloudflare)
|
|
- **SSL:** Auto-renewed via Caddy
|
|
|
|
#### Payments & Integrations
|
|
- **Stripe:** Live mode active
|
|
- **PayPal:** Live mode active
|
|
- **WordPress Sync:** Via igny8-wp-bridge plugin
|
|
- **Backup Automation:** Cron jobs on old VPS (backup-db.sh, backup-full.sh)
|
|
|
|
#### Health Check
|
|
- **Endpoint:** http://localhost:8010/api/v1/system/status/
|
|
- **Expected Response:** 200 OK with system status JSON
|
|
- **Frequency:** Manual or via monitoring
|
|
|
|
---
|
|
|
|
## 2. What to Build
|
|
|
|
### 2.1 Migration Objectives
|
|
|
|
Migrate IGNY8 v1.8.4 from old VPS to new VPS with **zero downtime** while maintaining:
|
|
- All production data integrity
|
|
- Continuous payment processing (Stripe/PayPal)
|
|
- WordPress plugin distribution and sync
|
|
- SSL/TLS without service interruption
|
|
- Backup automation
|
|
- Monitoring and logging
|
|
|
|
### 2.2 Three-Stage Migration Strategy
|
|
|
|
This migration is **not a direct cutover**. Instead, we run both VPS in parallel, test thoroughly via test subdomains, then flip DNS when fully healthy.
|
|
|
|
#### **Stage 1: Deploy & Test on New VPS (via test subdomains)**
|
|
|
|
**Prerequisite:** 00B has provisioned new VPS with infrastructure and created test subdomains.
|
|
|
|
**What Happens:**
|
|
- New VPS is fully operational with Docker, Caddy, PostgreSQL 18, Redis, etc.
|
|
- Test subdomains (`test-app.igny8.com`, `test-api.igny8.com`) already point to NEW VPS IP at current DNS provider
|
|
- Caddy on new VPS is configured to handle BOTH real domains AND test domains (same routing rules)
|
|
- Clone app code from GitHub, restore database via pg_dump/pg_restore (PG16 → PG18), transfer media files
|
|
- Deploy full Docker Compose stack on new VPS
|
|
- **Test EVERYTHING via test subdomains:** user login, payments (Stripe/PayPal), WordPress sync, content creation, API endpoints, Celery tasks
|
|
- Old VPS continues serving 100% of production traffic — **zero disruption to users**
|
|
|
|
**Duration:** 1-2 days (or until fully confident all features work)
|
|
|
|
**Rollback:** Simply disable test subdomains if critical issues found; old VPS unaffected.
|
|
|
|
#### **Stage 2: DNS Flip to New VPS (at current DNS provider)**
|
|
|
|
**Prerequisite:** New VPS is healthy and all tests passed via test subdomains.
|
|
|
|
**What Happens:**
|
|
1. Reduce TTL on real A records (`app.igny8.com`, `api.igny8.com`, `igny8.com`) to 60 seconds at current DNS provider
|
|
2. Wait 2-4 hours for old TTL to expire (original TTL was typically 3600+)
|
|
3. Change A records to point to NEW VPS IP
|
|
4. Production traffic flows to new VPS
|
|
5. Old VPS remains running as instant fallback
|
|
6. Monitor for 24-48 hours for any issues
|
|
7. Verify logs, error rates, payment processing, background jobs
|
|
|
|
**Duration:** 24-48 hours monitoring after DNS flip
|
|
|
|
**Rollback:** If anything breaks, flip A records back to old VPS IP instantly (TTL is now 60 seconds, so flip takes seconds to propagate). Old VPS was never shut down.
|
|
|
|
#### **Stage 3: Cloudflare Onboarding (AFTER 24-48h stable on new VPS)**
|
|
|
|
**Prerequisite:** Production traffic on new VPS is stable for 24-48 hours, error rates normal, payments flowing, logs clean.
|
|
|
|
**Important:** This stage happens AFTER we're confident on the new VPS. Cloudflare is a *security enhancement*, not a migration requirement.
|
|
|
|
**What Happens:**
|
|
1. Transfer domain's nameservers from current registrar to Cloudflare
|
|
2. Set up Cloudflare DNS zone with all A records pointing to new VPS IP
|
|
3. Enable Cloudflare proxy (orange cloud) for DDoS protection and CDN
|
|
4. Update Caddy to use Cloudflare DNS challenge for SSL:
|
|
- Install caddy-cloudflare module: `caddy download github.com/caddy-dns/cloudflare`
|
|
- Configure Caddyfile with `tls { dns cloudflare {...} }`
|
|
- OR keep Let's Encrypt HTTP challenge if Cloudflare is in Full (Strict) SSL mode
|
|
5. Remove test subdomains (no longer needed; production is now on real domains)
|
|
6. Configure Cloudflare settings:
|
|
- SSL/TLS Mode: Full (Strict)
|
|
- Always Use HTTPS: ON
|
|
- Auto Minify: JS, CSS, HTML
|
|
- Browser Cache TTL: 30 minutes
|
|
- Page Rules: any custom rules per requirements
|
|
|
|
**Duration:** A few hours (nameserver propagation up to 24 hours, but functional sooner)
|
|
|
|
**Rollback:** Nameserver rollback is slow (up to 24 hours propagation), so this stage is only done after confirmed stability.
|
|
|
|
---
|
|
|
|
## 3. Data Models & APIs
|
|
|
|
### 3.1 Database Schema (PostgreSQL 16 → 18)
|
|
|
|
The database schema itself does not change during migration. We use pg_dump and pg_restore to move the entire database from old VPS (PG 16) to new VPS (PG 18).
|
|
|
|
**Key Tables (not exhaustive):**
|
|
- `users` — User accounts
|
|
- `projects` — Projects/sites
|
|
- `stripe_subscriptions` — Payment records
|
|
- `integration_settings` — AI integration keys (GlobalIntegrationSettings)
|
|
- `wordpress_sync_logs` — Plugin sync history
|
|
- `celery_*` — Celery task tables
|
|
|
|
**Important:** Do NOT manually migrate tables. Use pg_dump/pg_restore with custom format.
|
|
|
|
### 3.2 Health Check API
|
|
|
|
**Endpoint:** `GET http://localhost:8010/api/v1/system/status/`
|
|
**Expected Response:**
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"version": "1.8.4",
|
|
"database": "connected",
|
|
"redis": "connected",
|
|
"celery": "ok"
|
|
}
|
|
```
|
|
|
|
Use this endpoint to verify both old and new VPS health before/after migration.
|
|
|
|
---
|
|
|
|
## 4. Implementation Steps
|
|
|
|
### 4.1 Stage 1: Deploy & Test on New VPS (test subdomains)
|
|
|
|
#### Prerequisites (from 00B)
|
|
- ✅ New VPS provisioned with Docker, Caddy, PostgreSQL 18, Redis
|
|
- ✅ Test subdomains (`test-app.igny8.com`, `test-api.igny8.com`) created at current DNS provider, pointing to new VPS IP
|
|
- ✅ Caddy on new VPS configured to route both real and test domains to containers
|
|
|
|
#### Step 1.1: Clone App Code from GitHub
|
|
|
|
On new VPS:
|
|
|
|
```bash
|
|
ssh root@<new-vps-ip>
|
|
|
|
cd /data/app/igny8
|
|
|
|
# Clone the consolidated repo (from 00A)
|
|
git clone https://github.com/yourusername/igny8-consolidated.git backend
|
|
cd backend
|
|
git checkout main # or appropriate branch
|
|
|
|
# Copy .env.example to .env and configure
|
|
cp .env.example .env
|
|
|
|
# Update .env for new VPS:
|
|
# - DB_HOST=igny8_postgres (Docker internal hostname)
|
|
# - DB_NAME=igny8_db
|
|
# - DB_USER=igny8
|
|
# - DB_PASSWORD=<secure password>
|
|
# - REDIS_HOST=igny8_redis
|
|
# - SECRET_KEY=<generate new>
|
|
# - ALLOWED_HOSTS=test-app.igny8.com,test-api.igny8.com,app.igny8.com,api.igny8.com,igny8.com
|
|
|
|
nano .env
|
|
```
|
|
|
|
#### Step 1.2: Backup Database on Old VPS & Transfer to New VPS
|
|
|
|
On old VPS:
|
|
|
|
```bash
|
|
ssh root@<old-vps-ip>
|
|
|
|
# Create database dump using custom format (required for major version upgrade)
|
|
pg_dump --format=custom --file=/tmp/igny8_db_backup.dump -U igny8 igny8_db
|
|
|
|
# Verify dump created
|
|
ls -lh /tmp/igny8_db_backup.dump
|
|
|
|
# Compress for transfer
|
|
gzip /tmp/igny8_db_backup.dump
|
|
```
|
|
|
|
Transfer to new VPS:
|
|
|
|
```bash
|
|
# From your local machine or jump host
|
|
scp root@<old-vps-ip>:/tmp/igny8_db_backup.dump.gz /tmp/
|
|
|
|
# Transfer to new VPS
|
|
scp /tmp/igny8_db_backup.dump.gz root@<new-vps-ip>:/tmp/
|
|
|
|
# On new VPS, decompress
|
|
ssh root@<new-vps-ip>
|
|
cd /tmp
|
|
gunzip igny8_db_backup.dump.gz
|
|
```
|
|
|
|
#### Step 1.3: Restore Database on New VPS (PG 18)
|
|
|
|
On new VPS:
|
|
|
|
```bash
|
|
# Wait for PostgreSQL container to be healthy
|
|
docker compose -f docker-compose.app.yml ps
|
|
|
|
# Restore using pg_restore (PostgreSQL 18 handles version upgrade automatically)
|
|
PGPASSWORD=<db-password> pg_restore --format=custom \
|
|
--host=localhost \
|
|
--port=5432 \
|
|
--username=igny8 \
|
|
--dbname=igny8_db \
|
|
/tmp/igny8_db_backup.dump
|
|
|
|
# Verify restore completed
|
|
PGPASSWORD=<db-password> psql --host=localhost --username=igny8 --dbname=igny8_db -c "SELECT COUNT(*) FROM users;"
|
|
|
|
# Run ANALYZE on all tables to update statistics
|
|
PGPASSWORD=<db-password> psql --host=localhost --username=igny8 --dbname=igny8_db -c "ANALYZE;"
|
|
```
|
|
|
|
#### Step 1.4: Transfer Media & Plugin Files
|
|
|
|
On new VPS:
|
|
|
|
```bash
|
|
# Create target directories
|
|
mkdir -p /data/app/igny8/media
|
|
mkdir -p /plugins/wordpress/source
|
|
|
|
# From old VPS, rsync media
|
|
rsync -avz --delete root@<old-vps-ip>:/data/app/igny8/media/ /data/app/igny8/media/
|
|
|
|
# Sync plugin files
|
|
rsync -avz --delete root@<old-vps-ip>:/plugins/wordpress/source/igny8-wp-bridge/ /plugins/wordpress/source/igny8-wp-bridge/
|
|
|
|
# Verify
|
|
ls -la /data/app/igny8/media/
|
|
ls -la /plugins/wordpress/source/igny8-wp-bridge/
|
|
```
|
|
|
|
#### Step 1.5: Deploy Docker Compose Stack on New VPS
|
|
|
|
On new VPS:
|
|
|
|
```bash
|
|
cd /data/app/igny8
|
|
|
|
# Copy docker-compose file (create if needed)
|
|
# Ensure it contains:
|
|
# - igny8_backend (Django)
|
|
# - igny8_frontend (Vite)
|
|
# - igny8_celery_worker
|
|
# - igny8_celery_beat
|
|
# - igny8_postgres (PostgreSQL 18)
|
|
# - igny8_redis
|
|
# - caddy
|
|
# - marketing, sites (if needed)
|
|
|
|
# Build and start
|
|
docker compose -f docker-compose.app.yml build
|
|
docker compose -f docker-compose.app.yml up -d
|
|
|
|
# Wait for containers to stabilize (10-15 seconds)
|
|
sleep 15
|
|
|
|
# Verify all containers running
|
|
docker compose -f docker-compose.app.yml ps
|
|
```
|
|
|
|
#### Step 1.6: Verify Caddy & SSL for Test Subdomains
|
|
|
|
On new VPS:
|
|
|
|
```bash
|
|
# Check Caddy is running
|
|
docker compose -f docker-compose.app.yml logs caddy | tail -50
|
|
|
|
# Verify test subdomains resolve and serve content
|
|
curl -I https://test-app.igny8.com
|
|
curl -I https://test-api.igny8.com
|
|
|
|
# Both should return 200 OK with valid SSL certificate
|
|
```
|
|
|
|
#### Step 1.7: Run Health Checks on Test Subdomains
|
|
|
|
```bash
|
|
# Health check via test API subdomain
|
|
curl -H "Host: test-api.igny8.com" http://localhost:8010/api/v1/system/status/
|
|
|
|
# Or if DNS is live
|
|
curl https://test-api.igny8.com/api/v1/system/status/
|
|
```
|
|
|
|
**Expected Response:**
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"version": "1.8.4",
|
|
"database": "connected",
|
|
"redis": "connected",
|
|
"celery": "ok"
|
|
}
|
|
```
|
|
|
|
#### Step 1.8: Manual Testing on Test Subdomains
|
|
|
|
Test via `https://test-app.igny8.com`:
|
|
|
|
- [ ] User login with test account
|
|
- [ ] Create new project/site
|
|
- [ ] View dashboard and analytics
|
|
- [ ] Test Stripe payment flow (use test card: 4242 4242 4242 4242)
|
|
- [ ] Test PayPal integration (sandbox mode)
|
|
- [ ] Verify WordPress plugin can be downloaded from `test-api.igny8.com`
|
|
- [ ] Verify WordPress sync works (if connected)
|
|
- [ ] Check background job processing (Celery logs)
|
|
- [ ] Verify media files upload and display correctly
|
|
- [ ] Check email notifications send correctly
|
|
- [ ] Verify AI integration keys are loaded from database
|
|
|
|
#### Step 1.9: Monitor Logs for 24 Hours
|
|
|
|
```bash
|
|
# Watch backend logs
|
|
docker compose -f docker-compose.app.yml logs -f igny8_backend
|
|
|
|
# Watch Celery logs
|
|
docker compose -f docker-compose.app.yml logs -f igny8_celery_worker
|
|
|
|
# Watch Caddy/SSL logs
|
|
docker compose -f docker-compose.app.yml logs -f caddy
|
|
```
|
|
|
|
**Look for:** Errors, warnings, slow queries, failed payment webhooks, SSL issues.
|
|
|
|
#### Stage 1 Acceptance Criteria
|
|
|
|
- ✅ Database restored with all tables and data intact (row counts match old VPS)
|
|
- ✅ Media files present and accessible
|
|
- ✅ Caddy SSL working for test subdomains with valid certificate
|
|
- ✅ Health check returns 200 OK with all systems "connected"
|
|
- ✅ User login works on test subdomains
|
|
- ✅ Payment processing (Stripe/PayPal) functional on test subdomains
|
|
- ✅ WordPress plugin distribution functional
|
|
- ✅ Background jobs (Celery) processing successfully
|
|
- ✅ No errors in application or Caddy logs over 24-hour monitoring period
|
|
- ✅ Old VPS still serving 100% production traffic with zero impact
|
|
|
|
#### Stage 1 Rollback
|
|
|
|
If critical issues found on new VPS:
|
|
|
|
```bash
|
|
# Simply delete/disable test subdomains at DNS provider
|
|
# (old VPS remains untouched)
|
|
|
|
# Or if needed, bring down new VPS containers
|
|
docker compose -f docker-compose.app.yml down
|
|
|
|
# Old VPS continues serving production
|
|
```
|
|
|
|
---
|
|
|
|
### 4.2 Stage 2: DNS Flip to New VPS (at current DNS provider)
|
|
|
|
#### Prerequisites
|
|
- ✅ Stage 1 testing complete; new VPS fully healthy
|
|
- ✅ All manual acceptance criteria passed
|
|
- ✅ 24+ hours of monitoring with no errors
|
|
|
|
#### Step 2.1: Reduce TTL on Real A Records
|
|
|
|
At current DNS provider (registrar panel or DNS management):
|
|
|
|
1. Navigate to DNS records for igny8.com
|
|
2. Find A records:
|
|
- `app.igny8.com` (currently points to old VPS IP)
|
|
- `api.igny8.com` (currently points to old VPS IP)
|
|
- `igny8.com` (currently points to old VPS IP)
|
|
3. Set TTL to **60 seconds** (reduce from typical 3600)
|
|
4. **Save changes**
|
|
|
|
```bash
|
|
# Verify TTL change via DNS lookup (run from local machine)
|
|
# TTL should now be ~60 seconds
|
|
nslookup app.igny8.com
|
|
dig app.igny8.com
|
|
|
|
# Check TTL value in output (usually shows in Answer section)
|
|
```
|
|
|
|
#### Step 2.2: Wait for TTL Expiration
|
|
|
|
- **Set TTL:** time T=0
|
|
- **Wait duration:** 2-4 hours (depending on previous TTL; being conservative)
|
|
- **During this time:** Continue monitoring old and new VPS, ensure no errors
|
|
|
|
#### Step 2.3: Flip A Records to New VPS IP
|
|
|
|
At current DNS provider, change A records:
|
|
|
|
- `app.igny8.com` → NEW VPS IP
|
|
- `api.igny8.com` → NEW VPS IP
|
|
- `igny8.com` → NEW VPS IP
|
|
|
|
**Save changes immediately.**
|
|
|
|
Verify DNS propagation:
|
|
|
|
```bash
|
|
# From local machine, verify new IP
|
|
nslookup app.igny8.com
|
|
# Should show NEW VPS IP within seconds to 2 minutes
|
|
|
|
# From another machine (ISP DNS may cache differently)
|
|
nslookup app.igny8.com
|
|
```
|
|
|
|
#### Step 2.4: Monitor Production for 24-48 Hours
|
|
|
|
On new VPS:
|
|
|
|
```bash
|
|
# Watch all logs
|
|
docker compose -f docker-compose.app.yml logs -f
|
|
|
|
# Monitor error rates
|
|
# - Check /data/app/logs/ for errors
|
|
# - Monitor Stripe/PayPal webhook logs
|
|
# - Check Celery task success rates
|
|
# - Monitor database slow query log
|
|
```
|
|
|
|
**Metrics to watch:**
|
|
- HTTP error rates (500, 502, 503, 504) — should remain <0.01%
|
|
- Payment webhook latency — should be <500ms
|
|
- Database connection pool — should not saturate
|
|
- Redis memory usage — should be stable
|
|
- Celery task failure rate — should remain 0%
|
|
|
|
#### Step 2.5: Verify Old VPS Can Be Kept Running as Fallback
|
|
|
|
Do NOT shut down old VPS yet. Keep it running as instant fallback for 48 hours.
|
|
|
|
If critical issue discovered:
|
|
|
|
```bash
|
|
# On new VPS, pull logs to diagnose
|
|
docker compose -f docker-compose.app.yml logs igny8_backend > /tmp/new-vps-logs.txt
|
|
|
|
# At DNS provider, flip A records back to OLD VPS IP
|
|
# (takes 30-60 seconds to propagate with TTL=60)
|
|
|
|
# Traffic reverts to old VPS instantly
|
|
# Old VPS never stopped, so no data loss
|
|
```
|
|
|
|
#### Step 2.6: Increase TTL Back to Normal (optional)
|
|
|
|
After 48 hours of stable operation, increase TTL back to 3600 or higher:
|
|
|
|
At DNS provider:
|
|
|
|
```bash
|
|
# Set TTL back to 3600 (1 hour)
|
|
# This reduces DNS query load once migration is stable
|
|
```
|
|
|
|
#### Stage 2 Acceptance Criteria
|
|
|
|
- ✅ DNS propagation successful within 5 minutes (verified globally)
|
|
- ✅ No 5xx errors spike after DNS flip
|
|
- ✅ Payment webhooks processing normally
|
|
- ✅ Background jobs running without errors
|
|
- ✅ User-facing errors < 0.01% of requests
|
|
- ✅ Database and Redis performing normally
|
|
- ✅ 24-48 hours of clean logs with no critical issues
|
|
- ✅ Old VPS remains running as hot standby
|
|
|
|
#### Stage 2 Rollback Procedure
|
|
|
|
If critical issue found within 48 hours:
|
|
|
|
1. **Diagnose the issue** using new VPS logs
|
|
2. **At DNS provider:** Change A records back to old VPS IP
|
|
3. **Wait:** 30-60 seconds for DNS to propagate (TTL is now 60 seconds)
|
|
4. **Traffic reverts** to old VPS (which never stopped)
|
|
5. **Fix issue** on new VPS in parallel
|
|
6. **Re-test** via test subdomains before attempting DNS flip again
|
|
|
|
---
|
|
|
|
### 4.3 Stage 3: Cloudflare Onboarding (AFTER 24-48h stable)
|
|
|
|
#### Prerequisites
|
|
- ✅ Stage 2 complete; new VPS has been live for 24-48 hours
|
|
- ✅ All monitoring metrics normal
|
|
- ✅ Error logs clean
|
|
- ✅ Payments flowing normally
|
|
- ✅ Test subdomains can now be decommissioned
|
|
|
|
#### Step 3.1: Transfer Nameservers to Cloudflare
|
|
|
|
**In Cloudflare Dashboard:**
|
|
|
|
1. Log into Cloudflare account
|
|
2. Add new site: `igny8.com`
|
|
3. Choose Free or paid plan
|
|
4. Cloudflare generates two nameservers (e.g., `ns1.cloudflare.com`, `ns2.cloudflare.com`)
|
|
|
|
**At current domain registrar:**
|
|
|
|
1. Navigate to nameserver settings for igny8.com
|
|
2. Replace current nameservers with Cloudflare nameservers
|
|
3. **Save changes**
|
|
|
|
**Verification:**
|
|
|
|
```bash
|
|
# Wait 5-30 minutes for nameserver propagation
|
|
# Then verify:
|
|
nslookup ns igny8.com
|
|
# Should show Cloudflare nameservers
|
|
|
|
# Verify DNS is resolving (should still point to new VPS IP)
|
|
nslookup app.igny8.com
|
|
# Should show NEW VPS IP
|
|
```
|
|
|
|
#### Step 3.2: Create DNS Records in Cloudflare
|
|
|
|
In Cloudflare Dashboard:
|
|
|
|
1. Go to **DNS** section
|
|
2. Create A records (all pointing to new VPS IP):
|
|
- Name: `app` | Type: A | IPv4: `<new-vps-ip>` | Proxied: Yes (orange cloud)
|
|
- Name: `api` | Type: A | IPv4: `<new-vps-ip>` | Proxied: Yes (orange cloud)
|
|
- Name: `@` (root) | Type: A | IPv4: `<new-vps-ip>` | Proxied: Yes (orange cloud)
|
|
|
|
3. Remove test subdomains (no longer needed):
|
|
- Delete `test-app` record
|
|
- Delete `test-api` record
|
|
|
|
**Verify DNS is resolving through Cloudflare:**
|
|
|
|
```bash
|
|
# Should show Cloudflare nameservers
|
|
dig app.igny8.com ns
|
|
|
|
# Should resolve to new VPS IP
|
|
nslookup app.igny8.com
|
|
```
|
|
|
|
#### Step 3.3: Configure Caddy for Cloudflare SSL
|
|
|
|
On new VPS, update Caddy to use Cloudflare DNS challenge for SSL (or keep Let's Encrypt HTTP if using Full Strict mode).
|
|
|
|
**Option A: Cloudflare DNS Challenge (recommended if using zone caching)**
|
|
|
|
1. Download Caddy with cloudflare module:
|
|
|
|
```bash
|
|
# SSH to new VPS
|
|
ssh root@<new-vps-ip>
|
|
|
|
# Download caddy with cloudflare module
|
|
# (This assumes you built caddy with xcaddy)
|
|
docker exec igny8_caddy caddy version
|
|
# If cloudflare module not present, rebuild Caddy image with:
|
|
# FROM caddy:builder as builder
|
|
# RUN builder -replace github.com/caddy-dns/cloudflare github.com/caddy-dns/cloudflare@latest
|
|
# FROM caddy:latest
|
|
# COPY --from=builder /usr/bin/caddy /usr/bin/caddy
|
|
```
|
|
|
|
2. Update Caddyfile to use Cloudflare DNS:
|
|
|
|
```caddyfile
|
|
# Caddyfile on new VPS
|
|
app.igny8.com api.igny8.com igny8.com {
|
|
reverse_proxy igny8_backend:8010 {
|
|
header_up Host {upstream_hostport}
|
|
}
|
|
|
|
tls {
|
|
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
|
|
}
|
|
}
|
|
```
|
|
|
|
3. Set Cloudflare API token in Docker Compose:
|
|
|
|
```yaml
|
|
# docker-compose.app.yml
|
|
services:
|
|
caddy:
|
|
environment:
|
|
CLOUDFLARE_API_TOKEN: <your-cloudflare-api-token>
|
|
```
|
|
|
|
4. Restart Caddy:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.app.yml restart caddy
|
|
```
|
|
|
|
**Option B: Let's Encrypt HTTP Challenge (if Cloudflare Full Strict SSL)**
|
|
|
|
If Cloudflare is in Full (Strict) SSL mode, Caddy can use HTTP challenge:
|
|
|
|
```caddyfile
|
|
app.igny8.com api.igny8.com igny8.com {
|
|
reverse_proxy igny8_backend:8010 {
|
|
header_up Host {upstream_hostport}
|
|
}
|
|
|
|
tls {
|
|
# Let's Encrypt will be used by default (HTTP challenge)
|
|
}
|
|
}
|
|
```
|
|
|
|
This works because Cloudflare's Full (Strict) mode trusts Caddy's self-signed certificate on the origin.
|
|
|
|
#### Step 3.4: Configure Cloudflare Settings
|
|
|
|
In Cloudflare Dashboard:
|
|
|
|
**SSL/TLS:**
|
|
- Mode: **Full (Strict)** — encrypts traffic between Cloudflare and origin (new VPS)
|
|
- Minimum TLS Version: **1.2**
|
|
- Automatic HTTPS Rewrites: **ON**
|
|
|
|
**Caching:**
|
|
- Default Cache TTL (Browser): **30 minutes**
|
|
- Cache on Browser Side: **ON**
|
|
|
|
**Speed:**
|
|
- Auto Minify: Enable **JavaScript, CSS, HTML**
|
|
- Brotli compression: **ON**
|
|
- Automatic platform optimization: **ON**
|
|
|
|
**Security:**
|
|
- Always Use HTTPS: **ON**
|
|
- HSTS: **max-age=31536000; includeSubDomains; preload**
|
|
- Opportunistic Encryption: **ON**
|
|
|
|
**Rules (if needed):**
|
|
- Add Page Rules for specific paths if required (e.g., exclude /api/webhooks from caching)
|
|
|
|
#### Step 3.5: Remove Test Subdomains
|
|
|
|
On new VPS, update Caddy to remove test subdomain routing (if they're still configured):
|
|
|
|
```caddyfile
|
|
# OLD (remove test subdomains)
|
|
# test-app.igny8.com test-api.igny8.com {
|
|
# ...
|
|
# }
|
|
|
|
# Keep only real domains
|
|
app.igny8.com api.igny8.com igny8.com {
|
|
reverse_proxy igny8_backend:8010
|
|
tls {
|
|
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
|
|
}
|
|
}
|
|
```
|
|
|
|
Restart Caddy:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.app.yml restart caddy
|
|
```
|
|
|
|
#### Step 3.6: Verify Cloudflare SSL & Performance
|
|
|
|
```bash
|
|
# Verify SSL is through Cloudflare (should see Cloudflare cert)
|
|
openssl s_client -connect app.igny8.com:443
|
|
|
|
# Verify HSTS header is present
|
|
curl -I https://app.igny8.com
|
|
# Should show: Strict-Transport-Security: max-age=31536000...
|
|
|
|
# Test API endpoint through Cloudflare
|
|
curl https://api.igny8.com/api/v1/system/status/
|
|
# Should return 200 OK
|
|
```
|
|
|
|
#### Stage 3 Acceptance Criteria
|
|
|
|
- ✅ Nameservers successfully pointing to Cloudflare
|
|
- ✅ DNS records created in Cloudflare (A records for app, api, root)
|
|
- ✅ SSL certificate valid and served through Cloudflare
|
|
- ✅ HTTPS enforced (HTTP redirects to HTTPS)
|
|
- ✅ Cloudflare caching enabled and working (check cache headers)
|
|
- ✅ No application errors after Cloudflare activation
|
|
- ✅ Payment processing still working through Cloudflare proxy
|
|
- ✅ API endpoints responding normally through Cloudflare
|
|
- ✅ Test subdomains removed and no longer needed
|
|
|
|
#### Stage 3 Rollback Procedure
|
|
|
|
If issues arise with Cloudflare:
|
|
|
|
1. **At Cloudflare Dashboard:** Disable proxy (gray cloud) for A records to DNS-only mode
|
|
2. **At registrar:** Revert nameservers back to previous provider
|
|
3. **Traffic reverts** to previous DNS setup (still on new VPS)
|
|
4. **Diagnose** Cloudflare issue
|
|
5. **Re-enable** Cloudflare after fixes are tested
|
|
|
|
---
|
|
|
|
## 5. Acceptance Criteria (Per Stage)
|
|
|
|
### Stage 1: Deploy & Test (Test Subdomains)
|
|
|
|
- Database row counts match old VPS (verify with SELECT COUNT queries)
|
|
- All media files present and accessible
|
|
- SSL certificates valid for test subdomains
|
|
- Health check endpoint returns 200 OK
|
|
- User authentication works via test subdomains
|
|
- Stripe test payments process successfully
|
|
- PayPal sandbox payments process successfully
|
|
- WordPress plugin downloadable and checksums match
|
|
- Celery background jobs process without errors (check logs)
|
|
- No critical errors in application logs over 24 hours
|
|
- Old VPS still serving production with zero disruption
|
|
|
|
### Stage 2: DNS Flip (Production Traffic)
|
|
|
|
- DNS propagation complete within 5 minutes globally
|
|
- No spike in 5xx errors after DNS flip
|
|
- Payment webhook latency remains <500ms
|
|
- Background job success rate remains >99%
|
|
- Database and Redis performance stable
|
|
- All manual testing passes on production domains
|
|
- 48 hours of monitoring with zero critical issues
|
|
- Old VPS remains running as hot fallback
|
|
|
|
### Stage 3: Cloudflare Onboarding
|
|
|
|
- Cloudflare nameservers active and resolving
|
|
- SSL certificate valid through Cloudflare
|
|
- HTTP automatically redirects to HTTPS
|
|
- Caching headers present and cache working
|
|
- Payment processing unaffected
|
|
- API endpoints respond normally through Cloudflare proxy
|
|
- HSTS header present
|
|
- No application errors related to Cloudflare
|
|
|
|
---
|
|
|
|
## 6. Claude Code Instructions
|
|
|
|
### For Stage 1 Deployment
|
|
|
|
```
|
|
Task: Deploy IGNY8 to new VPS and test via test subdomains
|
|
|
|
1. SSH to new VPS and verify Docker/PostgreSQL/Redis running
|
|
2. Clone GitHub repo to /data/app/igny8/backend
|
|
3. Create .env file with new VPS database credentials
|
|
4. On old VPS: pg_dump --format=custom to backup PostgreSQL 16
|
|
5. Transfer dump to new VPS and pg_restore to PostgreSQL 18
|
|
6. Run ANALYZE on all tables
|
|
7. rsync media and plugin files from old VPS
|
|
8. Docker compose build and up on new VPS
|
|
9. Verify Caddy SSL for test subdomains
|
|
10. Test health check: curl https://test-app.igny8.com/api/v1/system/status/
|
|
11. Manual testing: login, payments, WordPress plugin, background jobs
|
|
12. Monitor logs for 24 hours; report any errors
|
|
```
|
|
|
|
### For Stage 2 DNS Flip
|
|
|
|
```
|
|
Task: Flip DNS to new VPS after Stage 1 success
|
|
|
|
1. At DNS provider: Reduce TTL on A records to 60 seconds
|
|
2. Wait 2-4 hours for old TTL to expire
|
|
3. Change A records to new VPS IP:
|
|
- app.igny8.com
|
|
- api.igny8.com
|
|
- igny8.com
|
|
4. Verify DNS propagation globally (nslookup)
|
|
5. Monitor logs and error rates for 24-48 hours
|
|
6. Keep old VPS running as fallback
|
|
7. Document any issues; have rollback plan ready (flip A records back)
|
|
```
|
|
|
|
### For Stage 3 Cloudflare Onboarding
|
|
|
|
```
|
|
Task: Set up Cloudflare DNS and proxy (ONLY after 48h stable)
|
|
|
|
1. In Cloudflare: Add igny8.com, receive nameservers
|
|
2. At registrar: Replace nameservers with Cloudflare
|
|
3. In Cloudflare: Create A records (app, api, @) pointing to new VPS IP
|
|
4. In Cloudflare: Enable orange cloud (proxy) for all A records
|
|
5. On new VPS: Update Caddyfile with Cloudflare DNS challenge or HTTP challenge
|
|
6. Restart Caddy container
|
|
7. In Cloudflare: Configure SSL (Full Strict), HSTS, Auto Minify, caching
|
|
8. Verify SSL, HTTPS enforcement, cache headers
|
|
9. Test payment processing and API endpoints through Cloudflare
|
|
10. Remove test subdomains from DNS
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Rollback Procedures Summary
|
|
|
|
| Stage | Issue Discovered | Rollback Action | Downtime |
|
|
|-------|-----------------|-----------------|----------|
|
|
| 1 | New VPS has critical errors | Disable test subdomains; old VPS unaffected | 0 minutes |
|
|
| 2 | Production errors after DNS flip | Flip A records back to old VPS IP | <2 minutes (TTL=60s) |
|
|
| 3 | Cloudflare causes issues | Disable Cloudflare proxy (gray cloud) or revert nameservers | <5-30 minutes |
|
|
|
|
---
|
|
|
|
## 8. Timeline & Dependencies
|
|
|
|
```
|
|
Day 0-1: Stage 1 — Deploy, test via test subdomains
|
|
Day 1-2: Monitor new VPS; ensure fully healthy
|
|
Day 2: Stage 2 — Reduce TTL, wait for expiration
|
|
Day 2: Stage 2 — Flip DNS to new VPS
|
|
Day 2-4: Monitor production; old VPS as fallback
|
|
Day 4+: Stage 3 — Transfer to Cloudflare (after 48h stable)
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Related Documents
|
|
|
|
- **00A**: GitHub Repository Consolidation — app code merged into single repo
|
|
- **00B**: New VPS Infrastructure Setup — new VPS provisioned, test subdomains created
|
|
- **00D**: Staging Environment Setup — follows this migration doc
|
|
- **00E**: Old VPS Cleanup & Decommission — decommission after migration stable
|
|
|
|
---
|
|
|
|
**End of Document 00C**
|