27 KiB
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:
- Reduce TTL on real A records (
app.igny8.com,api.igny8.com,igny8.com) to 60 seconds at current DNS provider - Wait 2-4 hours for old TTL to expire (original TTL was typically 3600+)
- Change A records to point to NEW VPS IP
- Production traffic flows to new VPS
- Old VPS remains running as instant fallback
- Monitor for 24-48 hours for any issues
- 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:
- Transfer domain's nameservers from current registrar to Cloudflare
- Set up Cloudflare DNS zone with all A records pointing to new VPS IP
- Enable Cloudflare proxy (orange cloud) for DDoS protection and CDN
- 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
- Install caddy-cloudflare module:
- Remove test subdomains (no longer needed; production is now on real domains)
- 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 accountsprojects— Projects/sitesstripe_subscriptions— Payment recordsintegration_settings— AI integration keys (GlobalIntegrationSettings)wordpress_sync_logs— Plugin sync historycelery_*— 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:
{
"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:
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:
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:
# 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:
# 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:
# 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:
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:
# 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
# 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:
{
"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
# 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:
# 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):
- Navigate to DNS records for igny8.com
- 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)
- Set TTL to 60 seconds (reduce from typical 3600)
- Save changes
# 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 IPapi.igny8.com→ NEW VPS IPigny8.com→ NEW VPS IP
Save changes immediately.
Verify DNS propagation:
# 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:
# 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:
# 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:
# 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:
- Diagnose the issue using new VPS logs
- At DNS provider: Change A records back to old VPS IP
- Wait: 30-60 seconds for DNS to propagate (TTL is now 60 seconds)
- Traffic reverts to old VPS (which never stopped)
- Fix issue on new VPS in parallel
- 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:
- Log into Cloudflare account
- Add new site:
igny8.com - Choose Free or paid plan
- Cloudflare generates two nameservers (e.g.,
ns1.cloudflare.com,ns2.cloudflare.com)
At current domain registrar:
- Navigate to nameserver settings for igny8.com
- Replace current nameservers with Cloudflare nameservers
- Save changes
Verification:
# 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:
-
Go to DNS section
-
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)
- Name:
-
Remove test subdomains (no longer needed):
- Delete
test-apprecord - Delete
test-apirecord
- Delete
Verify DNS is resolving through Cloudflare:
# 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)
- Download Caddy with cloudflare module:
# 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
- Update Caddyfile to use Cloudflare DNS:
# 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}
}
}
- Set Cloudflare API token in Docker Compose:
# docker-compose.app.yml
services:
caddy:
environment:
CLOUDFLARE_API_TOKEN: <your-cloudflare-api-token>
- Restart Caddy:
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:
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):
# 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:
docker compose -f docker-compose.app.yml restart caddy
Step 3.6: Verify Cloudflare SSL & Performance
# 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:
- At Cloudflare Dashboard: Disable proxy (gray cloud) for A records to DNS-only mode
- At registrar: Revert nameservers back to previous provider
- Traffic reverts to previous DNS setup (still on new VPS)
- Diagnose Cloudflare issue
- 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