# IGNY8 Phase 0: VPS Provisioning (00B) **Status:** In Development **Date Created:** 2026-03-23 **Phase:** 0 (Infrastructure Setup) **Document ID:** 00B --- ## 1. Current State ### 1.1 Migration Context IGNY8 is transitioning from legacy infrastructure to a new KVM 4 VPS environment. The current production setup includes: - **Production Domains:** `app.igny8.com`, `api.igny8.com`, `igny8.com` (marketing) - **Staging Domains:** `staging.igny8.com`, `staging-api.igny8.com`, `staging-marketing.igny8.com` - **Current DNS Provider:** NOT Cloudflare — domains currently point to the OLD VPS via existing DNS provider - **Current Stack Health:** All services healthy on legacy infrastructure; zero downtime migration required ### 1.2 DNS Flow Clarification (CRITICAL) **This is NOT a Cloudflare-first migration.** The correct approach: 1. **Current State:** Domains resolve via current DNS provider → OLD VPS (working, healthy) 2. **During 00B (this doc):** - Provision new VPS infrastructure - Create TEMPORARY TEST subdomains (`test-app.igny8.com`, `test-api.igny8.com`, `test-marketing.igny8.com`) at the CURRENT DNS provider pointing to NEW VPS IP - Caddy on new server handles BOTH production domain names AND test subdomains (same containers, different hostnames) - Real production domains still resolve to old VPS via existing DNS — no traffic shift yet 3. **After 00B (covered in 00C):** DNS cutover happens — production domains flipped to new VPS 4. **Cloudflare Migration:** Happens ONLY AFTER new server is fully healthy and production DNS has been migrated (end of 00C) **SSL Certificates:** Caddy uses Let's Encrypt for auto-HTTPS on test subdomains. NO Cloudflare ACME tokens needed in this phase. ### 1.3 Previous Infrastructure Details The legacy setup has these key characteristics: - **Shared Services:** PostgreSQL, Redis, Caddy reverse proxy, PgAdmin, FileBrowser, Portainer - **Application Services:** Defined in separate docker-compose.app.yml per environment - **External Docker Network:** `igny8_net` (connects all services across compose files) - **Directory Structure:** `/data/stack/`, `/data/app/`, `/data/logs/`, `/data/backups/` - **Configuration:** Caddyfile for SSL/TLS termination, utility scripts in igny8-stack repo - **Installation Script:** `install.sh` in igny8-stack repo handles automated setup ### 1.4 Known Constraints - OS must be Ubuntu 24.04 LTS or later - SSH access only (no root password login) - Firewall restrictions (ports 80, 443, 22) - Docker-first architecture (no bare-metal services) - Test subdomains are temporary — removed after successful production DNS migration --- ## 2. Target Version Matrix This section is the single source of truth for all target versions across the entire V2 build. ### 2.1 Infrastructure & OS | Component | Version | Image/Package | Notes | |-----------|---------|----------------|-------| | Ubuntu | 24.04 LTS (Noble Numbat) | Official ISO | VPS base OS | | Docker Engine | 29.x | docker-ce | Latest stable from Docker repo | | Docker Compose | 2.x+ | docker-compose-plugin | Via Docker repo | | UFW Firewall | Latest | ufw | System package | ### 2.2 Shared Infrastructure Services | Service | Version | Image | Port (internal) | Port (public) | Volume | |---------|---------|-------|-----------------|---------------|--------| | PostgreSQL | 18 | `postgres:18-alpine` | 5432 | N/A | `/data/backups/postgres/` | | Redis | 8.x | `redis:8-alpine` | 6379 | N/A | `/data/backups/redis/` | | Caddy | 2.11.x | `caddy:2-alpine` | 80, 443 | 80, 443 | `/data/backups/caddy/` | | Portainer CE | 2.27+ | `portainer/portainer-ce:lts` | 9000 | Via Caddy | `/var/run/docker.sock` | | PgAdmin | Latest | `dpage/pgadmin4:latest` | 5050 | Via Caddy | N/A | | FileBrowser | Latest | `filebrowser/filebrowser:latest` | 8080 | Via Caddy | `/data/` | ### 2.3 Application Stack (reference — installed during 00C/00D) | Component | Version | Notes | |-----------|---------|-------| | Python | 3.14 | For backend Dockerfile | | Node.js | 24 LTS | For frontend Dockerfile | | Django | 6.0 | Backend framework | | Django REST Framework | Latest | API serializers | | Celery | 5.6 | Task queue | | Gunicorn | 25 | WSGI application server | | Vite | 8 | Frontend build tool | | React | Latest | Frontend library | --- ## 3. What to Build ### 3.1 Deliverables A fully provisioned, hardened KVM 4 VPS that: 1. Runs Ubuntu 24.04 LTS with essential system tools 2. Has Docker Engine 29.x and Docker Compose 2.x+ installed and ready 3. Implements UFW firewall with minimal port exposure (22, 80, 443) 4. Enforces SSH key-based authentication (no password login) 5. Hosts all shared infrastructure services (PostgreSQL 18, Redis 8, Caddy 2.11, Portainer 2.27+) 6. Maintains `/data/` directory structure for persistence 7. Caddy configured with routes for BOTH production AND test subdomains 8. Test subdomains resolve to new VPS and pass health checks 9. Passes health checks for all infrastructure services 10. Is ready for application deployment (see 00C for production, 00D for staging) ### 3.2 Out of Scope - Application container deployment (covered in 00C/00D) - Cloudflare DNS migration (covered at end of 00C) - Backup automation (post-Phase 0) - Monitoring/alerting setup (post-Phase 0) - Custom application configuration --- ## 4. Data Models / APIs ### 4.1 Service Specifications #### PostgreSQL 18 - **Port:** 5432 (internal, not exposed) - **Image:** `postgres:18-alpine` - **Volumes:** `/data/backups/postgres/` (data persistence) - **Environment Variables:** - `POSTGRES_USER=igny8` - `POSTGRES_PASSWORD=${DB_PASSWORD}` (from .env) - `POSTGRES_INITDB_ARGS=-c shared_preload_libraries=pg_stat_statements` #### Redis 8.x - **Port:** 6379 (internal, not exposed) - **Image:** `redis:8-alpine` - **Volumes:** `/data/backups/redis/` (persistence) - **Command:** `redis-server --appendonly yes` #### Caddy 2.11.x - **Ports:** 80/443 (exposed) - **Image:** `caddy:2-alpine` - **Config:** `/data/stack/Caddyfile` - **Volumes:** `/data/backups/caddy/` (SSL certs) - **Auto-HTTPS:** Enabled (via Let's Encrypt, not Cloudflare) - **Routes:** Production domains + test subdomains (same backend services) #### Portainer CE 2.27+ - **Port:** 9000 (internal, not exposed; proxied via Caddy) - **Image:** `portainer/portainer-ce:lts` - **Volumes:** `/var/run/docker.sock` (Docker daemon access) #### PgAdmin - **Port:** 5050 (internal, not exposed; proxied via Caddy) - **Image:** `dpage/pgadmin4:latest` - **Environment:** - `PGADMIN_DEFAULT_EMAIL=admin@igny8.com` - `PGADMIN_DEFAULT_PASSWORD=${PGADMIN_PASSWORD}` (from .env) #### FileBrowser - **Port:** 8080 (internal, not exposed; proxied via Caddy) - **Image:** `filebrowser/filebrowser:latest` - **Root:** `/data/` ### 4.2 Docker Network ``` Network Name: igny8_net Type: External bridge network Purpose: Allows both docker-compose.infra.yml and docker-compose.app.yml to communicate All infrastructure services connected to igny8_net ``` ### 4.3 Environment Variables Create `.env` file at `/data/stack/.env`: ``` # PostgreSQL DB_PASSWORD= DB_USER=igny8 # PgAdmin PGADMIN_PASSWORD= # Portainer PORTAINER_PASSWORD= # Application Domains (for reference in Caddyfile) PROD_DOMAIN=igny8.com PROD_APP_DOMAIN=app.igny8.com PROD_API_DOMAIN=api.igny8.com STAGING_DOMAIN=staging.igny8.com STAGING_APP_DOMAIN=staging.igny8.com STAGING_API_DOMAIN=staging-api.igny8.com # Test Domains (temporary, point to new VPS during migration) TEST_DOMAIN=test-marketing.igny8.com TEST_APP_DOMAIN=test-app.igny8.com TEST_API_DOMAIN=test-api.igny8.com ``` **NOTE:** No `CF_API_TOKEN` or `CF_ZONE_ID` in this phase. Cloudflare integration happens in 00C. ### 4.4 Docker Compose Files **docker-compose.infra.yml** (shared infrastructure): - PostgreSQL 18 - Redis 8.x - Caddy 2.11.x - Portainer CE 2.27+ - PgAdmin - FileBrowser **docker-compose.app.yml** (application services, per environment): - Production application containers (deployed in 00C) - Staging application containers (deployed in 00D) - Linked to `igny8_net` external network --- ## 5. Implementation Steps ### 5.1 Pre-Provisioning Checklist Before starting, have ready: - [ ] VPS login credentials (IP address, initial SSH access) - [ ] Current DNS provider credentials (to add test subdomains) - [ ] Strong passwords for DB_PASSWORD, PGADMIN_PASSWORD, PORTAINER_PASSWORD - [ ] SSH public key for hardening - [ ] igny8-stack repository cloned locally or accessible - [ ] Plan for test subdomain creation at current DNS provider ### 5.2 Phase 0: System Provisioning #### Step 0.1: Initial Access & Updates ```bash # SSH into VPS as root (initial access) ssh root@ # Update system packages apt-get update apt-get upgrade -y # Install essential utilities apt-get install -y \ curl \ wget \ git \ vim \ htop \ net-tools \ ufw \ openssh-server \ openssh-client # Set timezone timedatectl set-timezone UTC # Verify Ubuntu 24.04 LTS lsb_release -a # Should output: Release: 24.04 # Verify kernel uname -r ``` #### Step 0.2: Create Non-Root User ```bash # Create new user (replace 'igny8' with preferred username) adduser --disabled-password --gecos "" igny8 # Add user to sudoers usermod -aG sudo igny8 # Create SSH directory for user mkdir -p /home/igny8/.ssh chmod 700 /home/igny8/.ssh ``` #### Step 0.3: SSH Hardening ```bash # (From local machine) Copy public SSH key to VPS ssh-copy-id -i ~/.ssh/id_rsa.pub igny8@ # SSH into VPS as new user ssh igny8@ # Edit SSH daemon config (as igny8 with sudo) sudo vi /etc/ssh/sshd_config # Make these changes: # PermitRootLogin no # PasswordAuthentication no # PubkeyAuthentication yes # X11Forwarding no # PrintMotd no # AcceptEnv LANG LC_* # Subsystem sftp /usr/lib/openssh/sftp-server # Restart SSH daemon sudo systemctl restart sshd # Test from another terminal to ensure key-based auth works ssh igny8@ # Should connect without password ``` #### Step 0.4: Configure UFW Firewall ```bash # Enable UFW sudo ufw enable # Set default policies sudo ufw default deny incoming sudo ufw default allow outgoing # Allow SSH (critical before locking down) sudo ufw allow 22/tcp # Allow HTTP/HTTPS (for Caddy) sudo ufw allow 80/tcp sudo ufw allow 443/tcp # Verify rules sudo ufw status verbose # Output should show: # To Action From # -- ------ ---- # 22/tcp ALLOW Anywhere # 80/tcp ALLOW Anywhere # 443/tcp ALLOW Anywhere # 22/tcp (v6) ALLOW Anywhere (v6) # 80/tcp (v6) ALLOW Anywhere (v6) # 443/tcp (v6) ALLOW Anywhere (v6) ``` #### Step 0.5: Install Docker & Docker Compose ```bash # Add Docker repository sudo apt-get update sudo apt-get install -y \ apt-transport-https \ ca-certificates \ curl \ gnupg \ lsb-release # Add Docker GPG key curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # Add Docker repository echo \ "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \ https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # Install Docker Engine 29.x and Docker Compose 2.x sudo apt-get update sudo apt-get install -y \ docker-ce \ docker-ce-cli \ containerd.io \ docker-compose-plugin # Verify Docker installation sudo docker --version # Should show: Docker version 29.x.x sudo docker compose version # Should show: Docker Compose version 2.x.x # Add user to docker group (logout & login to take effect) sudo usermod -aG docker igny8 # Test Docker without sudo (after logout/login) docker run hello-world ``` #### Step 0.6: Create Directory Structure ```bash # Create /data hierarchy sudo mkdir -p /data/stack sudo mkdir -p /data/app sudo mkdir -p /data/logs sudo mkdir -p /data/backups/postgres sudo mkdir -p /data/backups/redis sudo mkdir -p /data/backups/caddy # Set ownership and permissions sudo chown -R igny8:igny8 /data sudo chmod -R 755 /data ``` #### Step 0.7: Clone igny8-stack Repository ```bash # Navigate to /data/stack cd /data/stack # Clone repository (adjust URL as needed) git clone https://github.com/igny8/igny8-stack.git . # Verify repository contents ls -la /data/stack # Should contain: docker-compose.infra.yml, Caddyfile, install.sh, etc. ``` ### 5.3 Phase 1: Docker Network & Infrastructure Setup #### Step 1.1: Create External Docker Network ```bash # Create the igny8_net external bridge network docker network create igny8_net # Verify network creation docker network ls | grep igny8_net docker network inspect igny8_net ``` #### Step 1.2: Configure Environment Variables ```bash # Create .env file at /data/stack/.env cd /data/stack cat > .env << 'EOF' # PostgreSQL DB_PASSWORD= DB_USER=igny8 # PgAdmin PGADMIN_PASSWORD= # Portainer PORTAINER_PASSWORD= # Application Domains (for reference in Caddyfile) PROD_DOMAIN=igny8.com PROD_APP_DOMAIN=app.igny8.com PROD_API_DOMAIN=api.igny8.com STAGING_DOMAIN=staging.igny8.com STAGING_APP_DOMAIN=staging.igny8.com STAGING_API_DOMAIN=staging-api.igny8.com # Test Domains (temporary, point to new VPS during migration) TEST_DOMAIN=test-marketing.igny8.com TEST_APP_DOMAIN=test-app.igny8.com TEST_API_DOMAIN=test-api.igny8.com EOF # Secure .env file permissions chmod 600 .env ``` #### Step 1.3: Configure Caddyfile ```bash # Edit /data/stack/Caddyfile cat > /data/stack/Caddyfile << 'EOF' # Global settings { email admin@igny8.com } # Production domains (DNS still on old VPS — these won't receive traffic yet) app.igny8.com { reverse_proxy igny8_backend:8010 } api.igny8.com { reverse_proxy igny8_backend:8010 } igny8.com, www.igny8.com { reverse_proxy igny8_marketing:3000 } # Test domains (DNS points to THIS new VPS — use these for validation) test-app.igny8.com { reverse_proxy igny8_backend:8010 } test-api.igny8.com { reverse_proxy igny8_backend:8010 } test-marketing.igny8.com { reverse_proxy igny8_marketing:3000 } # Staging domains staging.igny8.com, www.staging.igny8.com { reverse_proxy igny8_staging_marketing:3000 } staging-app.igny8.com { reverse_proxy igny8_staging_backend:8010 } staging-api.igny8.com { reverse_proxy igny8_staging_backend:8010 } # Management services portainer.igny8.com { reverse_proxy portainer:9000 } pgadmin.igny8.com { reverse_proxy pgadmin:5050 } filebrowser.igny8.com { reverse_proxy filebrowser:8080 } EOF # Verify Caddyfile syntax docker run --rm -v /data/stack/Caddyfile:/etc/caddy/Caddyfile caddy:2-alpine caddy validate ``` **Key Points:** - Production domains (app.igny8.com, api.igny8.com, igny8.com) are configured but DNS still points to old VPS - Test subdomains (test-app.igny8.com, test-api.igny8.com, test-marketing.igny8.com) route to SAME backend containers — they're just different hostnames - Caddy uses Let's Encrypt for automatic HTTPS on test domains - No Cloudflare ACME tokens required in this phase #### Step 1.4: Run Docker Compose (Infrastructure) ```bash # Navigate to stack directory cd /data/stack # Start infrastructure services docker compose -f docker-compose.infra.yml up -d # Verify containers are running docker ps # Expected output should show: # - postgres (postgres:18-alpine) # - redis (redis:8-alpine) # - caddy (caddy:2-alpine) # - portainer (portainer/portainer-ce:lts) # - pgadmin (dpage/pgadmin4:latest) # - filebrowser (filebrowser/filebrowser:latest) # Check container logs for errors docker compose -f docker-compose.infra.yml logs -f # Common issues: # - Port 5432 already in use: Check existing postgres instances # - Network error: Ensure igny8_net was created (Step 1.1) # - Permission denied on volumes: Check /data ownership (Step 0.6) ``` #### Step 1.5: Test Subdomain DNS Configuration At your CURRENT DNS provider (NOT Cloudflare): 1. Create A records for test subdomains pointing to NEW VPS IP: - `test-app.igny8.com` → `` (not proxied, DNS only) - `test-api.igny8.com` → `` (not proxied, DNS only) - `test-marketing.igny8.com` → `` (not proxied, DNS only) 2. Leave production domains unchanged: - `app.igny8.com` → OLD VPS IP (still healthy) - `api.igny8.com` → OLD VPS IP (still healthy) - `igny8.com` → OLD VPS IP (still healthy) 3. Verify DNS propagation: ```bash nslookup test-app.igny8.com nslookup test-api.igny8.com nslookup test-marketing.igny8.com # All should resolve to NEW_VPS_IP ``` ### 5.4 Phase 2: Verification & Health Checks #### Step 2.1: Container Health Verification ```bash # Check all infrastructure containers docker ps --format "table {{.Names}}\t{{.Status}}" # Expected output: # NAMES STATUS # caddy Up X minutes # portainer Up X minutes # pgadmin Up X minutes # filebrowser Up X minutes # postgres Up X minutes # redis Up X minutes # Check logs for errors docker compose -f docker-compose.infra.yml logs | grep -i "error" # Should return no results or only informational messages ``` #### Step 2.2: Network Connectivity Tests ```bash # Verify igny8_net connectivity docker network inspect igny8_net # Test PostgreSQL connectivity from another container docker run --rm --network igny8_net postgres:18-alpine \ psql -h postgres -U igny8 -c "SELECT version();" # Prompt for password (DB_PASSWORD from .env) # Test Redis connectivity docker run --rm --network igny8_net redis:8-alpine \ redis-cli -h redis PING # Should return "PONG" ``` #### Step 2.3: Test Subdomain HTTPS Tests ```bash # Test Let's Encrypt certificate provisioning on test domains curl -I https://test-app.igny8.com # Expected: 200 or 502 (container not deployed yet, which is fine) # Important: Should NOT show certificate errors curl -I https://test-api.igny8.com # Expected: 200 or 502 curl -I https://test-marketing.igny8.com # Expected: 200 or 502 # Verify certificate details openssl s_client -connect test-app.igny8.com:443 -servername test-app.igny8.com < /dev/null | grep -A 2 "Issuer" # Should show: Issuer: C = US, O = Let's Encrypt, CN = R3 (or similar) # Check Caddy logs for certificate provisioning docker logs caddy | grep -i "test-app\|test-api\|test-marketing" ``` #### Step 2.4: Production Domain Verification ```bash # Verify production domains still resolve to OLD VPS (they should not change yet) nslookup app.igny8.com nslookup api.igny8.com nslookup igny8.com # Should show OLD VPS IP, not NEW VPS IP # This confirms zero impact on production during provisioning ``` #### Step 2.5: System Resource Verification ```bash # Check disk usage df -h /data # Check memory usage free -h # Check Docker disk usage docker system df # Monitor running processes top -b -n 1 | head -n 20 ``` #### Step 2.6: Backup & Persistence Verification ```bash # Verify database file exists ls -lh /data/backups/postgres/ # Verify Redis persistence ls -lh /data/backups/redis/ # Check Caddy certificate storage ls -lh /data/backups/caddy/ # All directories should contain actual data files ``` --- ## 6. Acceptance Criteria The VPS provisioning is complete when ALL of the following are satisfied: ### 6.1 System Requirements - [ ] Ubuntu 24.04 LTS confirmed via `lsb_release -a` - [ ] SSH access via key-based authentication only - [ ] Root password login disabled - [ ] UFW firewall enabled with only ports 22, 80, 443 open - [ ] System time synchronized (UTC timezone) - [ ] Non-root user (`igny8`) created with sudo access ### 6.2 Docker & Containerization - [ ] Docker Engine 29.x running (`docker --version`) - [ ] Docker Compose 2.x+ installed (`docker compose version`) - [ ] User `igny8` can run docker commands without sudo - [ ] External network `igny8_net` exists and is accessible ### 6.3 Directory Structure - [ ] `/data/stack/` contains igny8-stack repository with docker-compose.infra.yml and Caddyfile - [ ] `/data/app/` exists and is owned by igny8:igny8 - [ ] `/data/logs/` exists and is writable - [ ] `/data/backups/postgres/` contains PostgreSQL data - [ ] `/data/backups/redis/` contains Redis persistence files - [ ] `/data/backups/caddy/` contains Caddy certificates and Let's Encrypt data ### 6.4 Infrastructure Services - [ ] PostgreSQL 18 container running and healthy - Verification: `docker exec postgres psql -U igny8 -c "SELECT 1"` - [ ] Redis 8.x container running and healthy - Verification: `docker exec redis redis-cli PING` returns `PONG` - [ ] Caddy 2.11.x container running and listening on 80/443 - Verification: `curl -I https://test-app.igny8.com` succeeds (no cert errors) - [ ] Portainer container running and accessible - Verification: `curl -I https://portainer.igny8.com` succeeds - [ ] PgAdmin container running and accessible - Verification: `curl -I https://pgadmin.igny8.com` succeeds - [ ] FileBrowser container running and accessible - Verification: `curl -I https://filebrowser.igny8.com` succeeds ### 6.5 DNS & Test Subdomains - [ ] Test subdomains created at CURRENT DNS provider pointing to NEW VPS IP - [ ] Production domains UNCHANGED at DNS provider (still point to OLD VPS) - [ ] DNS propagates successfully: `nslookup test-app.igny8.com` resolves to NEW VPS IP - [ ] HTTPS certificates auto-provisioned by Caddy via Let's Encrypt on test subdomains - [ ] Zero impact on production: all production domains still accessible on old VPS ### 6.6 Security & Hardening - [ ] SSH daemon configured for key-only authentication - [ ] Root account password login disabled - [ ] UFW firewall enforced with minimal rule set - [ ] `.env` file has 600 permissions (readable only by owner) - [ ] `/data/` directory owned by igny8:igny8 - [ ] No exposed ports beyond 22, 80, 443 ### 6.7 Documentation & Handoff - [ ] This document (00B) completed and validated - [ ] Next phases (00C production migration, 00D staging setup) have actionable prerequisites - [ ] All credentials stored securely (not in git, only in .env with 600 permissions) - [ ] Docker Compose files checked into source control --- ## 7. Claude Code Instructions These instructions enable Claude Code to provision the VPS or verify existing provisioning. ### 7.1 Provisioning Automation **Objective:** Automate VPS setup from SSH access **Files to Create/Modify:** - `/data/stack/.env` (environment variables — NO Cloudflare tokens) - `/data/stack/docker-compose.infra.yml` (infrastructure services) - `/data/stack/Caddyfile` (reverse proxy config with test subdomains) **Shell Scripts to Execute:** 1. System update & security setup (Steps 0.1-0.4) 2. Docker installation (Step 0.5) 3. Directory structure creation (Step 0.6) 4. Repository clone (Step 0.7) 5. Network & Docker Compose startup (Steps 1.1-1.4) **Inputs Needed:** - VPS IP address - SSH key path - Strong random passwords for services - Current DNS provider credentials (for test subdomain creation) **Expected Outputs:** - Fully running infrastructure stack with PostgreSQL 18, Redis 8, Caddy 2.11 - All containers healthy - Test subdomains accessible via HTTPS (Let's Encrypt certs) - Production domains unchanged on old VPS - No errors in logs - Passes all acceptance criteria (Section 6) ### 7.2 Verification Automation **Objective:** Verify existing provisioning is healthy **Checks to Perform:** 1. SSH connectivity to VPS 2. UFW firewall rules (22, 80, 443 only) 3. Docker services running count 4. Network igny8_net exists 5. PostgreSQL 18 responsive 6. Redis 8 responsive 7. Caddy 2.11 listening on 80/443 8. DNS resolution for test subdomains (NEW VPS) 9. DNS resolution for production domains (OLD VPS — should not change) 10. HTTPS certificate provisioning on test subdomains 11. No Cloudflare API tokens in .env (should not exist yet) **Expected Outcomes:** - All checks pass - No orphaned containers - No disk space warnings - Logs show no errors - Test subdomains fully functional - Production domains unaffected ### 7.3 Integration with Downstream Phases **Dependencies for 00C (Production Migration):** - Infrastructure provisioning complete (this phase) - docker-compose.app.yml ready in `/data/app/` - Production application images built and available - Test subdomains validated and working - Plan for DNS cutover (moving production domains to new VPS) **Dependencies for 00D (Staging Setup):** - Infrastructure provisioning complete (this phase) - Staging application images available - docker-compose.app.yml configured for staging - Staging domains configured in Caddyfile (already done in this phase) ### 7.4 Troubleshooting Runbook **If PostgreSQL 18 fails to start:** ```bash # Check volume permissions ls -lh /data/backups/postgres/ # Check logs docker logs postgres | tail -20 # Verify image version docker images | grep postgres # Should show postgres:18-alpine # Recreate volume if corrupted docker compose -f docker-compose.infra.yml down docker compose -f docker-compose.infra.yml up -d postgres ``` **If Caddy fails to provision Let's Encrypt certificates on test subdomains:** ```bash # Verify DNS resolution for test subdomains nslookup test-app.igny8.com # Should resolve to NEW_VPS_IP # Check Caddy logs docker logs caddy | grep -i "test-app\|error\|acme" # Verify test subdomain A records exist at current DNS provider # (NOT Cloudflare — your current DNS provider) # Force certificate renewal docker compose -f docker-compose.infra.yml restart caddy ``` **If Docker network unreachable:** ```bash # Verify network exists docker network ls | grep igny8_net # Recreate if missing docker network create igny8_net # Restart services docker compose -f docker-compose.infra.yml restart ``` **If SSH key auth fails:** ```bash # Verify SSH config sudo cat /etc/ssh/sshd_config | grep -i "pubkey\|password" # Check authorized_keys cat ~/.ssh/authorized_keys # Ensure correct permissions chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys ``` **If production domains become inaccessible:** ```bash # Verify production domains still resolve to OLD VPS IP nslookup app.igny8.com nslookup api.igny8.com nslookup igny8.com # These should NOT resolve to NEW VPS IP at this stage # If they do, reverse the DNS change immediately # Verify old VPS is still running (contact infrastructure team) ``` --- ## 8. Cross-References - **00A:** IGNY8 Phase 0 Overview (prerequisites, timeline, team) - **00C:** Production Migration (deploying production apps + DNS cutover) - **00D:** Staging Setup (deploying staging environment) - **Appendix:** igny8-stack repository documentation --- ## 9. Sign-Off **Document Owner:** Infrastructure Team **Last Updated:** 2026-03-23 **Approval Status:** Pending Review | Role | Name | Signature | Date | |------|------|-----------|------| | Infrastructure Lead | — | — | — | | DevOps Engineer | — | — | — | | Project Manager | — | — | — | --- ## Appendix: Quick Reference Commands ### Essential Docker Commands ```bash # View all services docker ps -a # View infrastructure logs docker compose -f docker-compose.infra.yml logs -f # Restart a service docker compose -f docker-compose.infra.yml restart # Execute command in container docker exec # Connect to PostgreSQL docker exec -it postgres psql -U igny8 -d igny8 # Check network connectivity docker network inspect igny8_net ``` ### Essential System Commands ```bash # Check disk usage df -h /data # Check memory free -h # Monitor processes htop # View firewall rules sudo ufw status verbose # Check SSH connectivity ssh -v igny8@ # DNS propagation check dig test-app.igny8.com +short dig app.igny8.com +short ``` ### Backup & Recovery ```bash # Backup PostgreSQL data docker exec postgres pg_dump -U igny8 igny8 > backup.sql # Backup Redis data docker exec redis redis-cli --rdb /data/backups/redis/dump-$(date +%s).rdb # List all backups ls -lh /data/backups/ ``` ### DNS Verification (Critical During Migration) ```bash # Verify test subdomains point to NEW VPS (should work) nslookup test-app.igny8.com nslookup test-api.igny8.com nslookup test-marketing.igny8.com # Verify production domains point to OLD VPS (should NOT change yet) nslookup app.igny8.com nslookup api.igny8.com nslookup igny8.com # Test HTTPS on test subdomains curl -I https://test-app.igny8.com # Do NOT test production domains on new VPS yet (they're still on old VPS) ```