# Service Migration: n8n (prod + test) + Formbricks → OVH > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Migrate three Docker Compose services (n8n production, n8n-test, formbricks) from hyperthrive-docker to OVH (jared@15.204.247.153), preserving all data, coordinated around a user-initiated DNS cutover. **Architecture:** Each service is stopped on the source to capture a consistent volume snapshot, volumes are exported as tarballs and transferred over SSH, service directories are created on OVH with adapted configs (network and cert resolver names differ between servers), volumes are imported, services are started and health-checked, then the user cuts DNS. Tasks 1, 2, and 3 are fully independent and can run in parallel. **Tech Stack:** Docker Compose v2, Traefik v3.6, PostgreSQL 16, pgvector v0.5.1, n8n, Formbricks --- ## Config Differences: Source → Target | Setting | hyperthrive-docker (source) | OVH / 15.204.247.153 (target) | |---|---|---| | External Traefik network | `reverse-proxy-nw` | `traefik` | | Traefik cert resolver | `myresolver` | `letsencrypt` | | Services path | `~/services/` | `~/services/` | --- ## Task 1: Migrate n8n Production — n8n.hyperthrive.io **Model tier:** Sonnet **Files on OVH:** - Create: `~/services/n8n/docker-compose.yml` - Create: `~/services/n8n/.env` - Create: `~/services/n8n/init-data.sh` **Volumes to migrate:** - `n8n_db_storage` → PostgreSQL data (postgres container) - `n8n_n8n_storage` → n8n app data and encryption key (n8n container) > ⚠️ Both volumes MUST be migrated together. The n8n app volume contains the encryption key used to protect credentials stored in the database. Migrating only the database will leave credentials unreadable. - [ ] **Step 1: Stop n8n stack on source to get consistent data** ```bash ssh hyperthrive-docker "cd ~/services/n8n && docker compose stop" ``` Expected: containers stop, no error - [ ] **Step 2: Export postgres volume from source** ```bash ssh hyperthrive-docker "docker run --rm -v n8n_db_storage:/data alpine tar czf - -C /data ." > /tmp/n8n_db_storage.tar.gz ``` Expected: file `/tmp/n8n_db_storage.tar.gz` created locally, non-zero size - [ ] **Step 3: Export n8n app data volume from source** ```bash ssh hyperthrive-docker "docker run --rm -v n8n_n8n_storage:/data alpine tar czf - -C /data ." > /tmp/n8n_n8n_storage.tar.gz ``` Expected: file `/tmp/n8n_n8n_storage.tar.gz` created locally, non-zero size - [ ] **Step 4: Restart n8n on source (minimise source downtime)** ```bash ssh hyperthrive-docker "cd ~/services/n8n && docker compose start" ``` Expected: containers running again - [ ] **Step 5: Create service directory on OVH** ```bash ssh jared@15.204.247.153 "mkdir -p ~/services/n8n" ``` - [ ] **Step 6: Write docker-compose.yml on OVH** ```bash ssh jared@15.204.247.153 "cat > ~/services/n8n/docker-compose.yml" << 'COMPOSE' volumes: db_storage: n8n_storage: services: postgres: image: postgres:16 restart: always environment: - POSTGRES_USER - POSTGRES_PASSWORD - POSTGRES_DB - POSTGRES_NON_ROOT_USER - POSTGRES_NON_ROOT_PASSWORD volumes: - db_storage:/var/lib/postgresql/data - ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh healthcheck: test: ['CMD-SHELL', 'pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}'] interval: 5s timeout: 5s retries: 10 networks: - internal n8n: image: docker.n8n.io/n8nio/n8n:latest restart: always environment: - DB_TYPE=postgresdb - DB_POSTGRESDB_HOST=postgres - DB_POSTGRESDB_PORT=5432 - DB_POSTGRESDB_DATABASE=${POSTGRES_DB} - DB_POSTGRESDB_USER=${POSTGRES_NON_ROOT_USER} - DB_POSTGRESDB_PASSWORD=${POSTGRES_NON_ROOT_PASSWORD} - N8N_HOST=${N8N_HOST} - N8N_PORT=5678 - N8N_PROTOCOL=https - N8N_TRUSTED_PROXIES=1 - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true - N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true - EXECUTIONS_DATA_MAX_AGE=72 - NODE_ENV=production - WEBHOOK_URL=https://${N8N_HOST} - QUEUE_HEALTH_CHECK_ACTIVE=true - NODE_FUNCTION_ALLOW_BUILTIN=crypto healthcheck: test: ["CMD", "wget", "--no-check-certificate", "--spider", "http://127.0.0.1:5678/healthz"] interval: 12s timeout: 10s retries: 3 start_period: 15s volumes: - n8n_storage:/home/node/.n8n depends_on: postgres: condition: service_healthy networks: - internal - traefik labels: - "traefik.enable=true" - "traefik.http.routers.n8n.rule=Host(`n8n.hyperthrive.io`) && !PathPrefix(`/mcp`)" - "traefik.http.routers.n8n.entrypoints=websecure" - "traefik.http.routers.n8n.tls.certresolver=letsencrypt" - "traefik.http.routers.n8n.service=n8n" - "traefik.http.routers.n8n-mcp.rule=Host(`n8n.hyperthrive.io`) && PathPrefix(`/mcp`)" - "traefik.http.routers.n8n-mcp.entrypoints=websecure" - "traefik.http.routers.n8n-mcp.tls.certresolver=letsencrypt" - "traefik.http.routers.n8n-mcp.service=n8n" - "traefik.http.routers.n8n-mcp.middlewares=no-buffer@docker" - "traefik.http.middlewares.no-buffer.headers.customResponseHeaders.X-Accel-Buffering=no" - "traefik.http.middlewares.no-buffer.headers.customResponseHeaders.Cache-Control=no-cache" - "traefik.http.middlewares.no-buffer.compress=false" - "traefik.http.services.n8n.loadbalancer.server.port=5678" - "traefik.docker.network=traefik" logging: driver: "json-file" options: max-size: "10m" max-file: "3" networks: internal: traefik: external: true COMPOSE ``` - [ ] **Step 7: Write .env on OVH** ```bash ssh jared@15.204.247.153 "cat > ~/services/n8n/.env" << 'ENV' POSTGRES_USER=n8n_user_pg POSTGRES_PASSWORD=JS7FJQaVj8dhPo3j5#G69 POSTGRES_DB=n8n POSTGRES_NON_ROOT_USER=n8n_non_root_pg POSTGRES_NON_ROOT_PASSWORD=WzG$9GqluVmywLCiAuqTz N8N_HOST=n8n.hyperthrive.io N8N_VERSION=1.71.3 ENV ``` - [ ] **Step 8: Write init-data.sh on OVH** ```bash ssh jared@15.204.247.153 "cat > ~/services/n8n/init-data.sh" << 'SCRIPT' #!/bin/bash set -e; if [ -n "${POSTGRES_NON_ROOT_USER:-}" ] && [ -n "${POSTGRES_NON_ROOT_PASSWORD:-}" ]; then psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL CREATE USER ${POSTGRES_NON_ROOT_USER} WITH PASSWORD '${POSTGRES_NON_ROOT_PASSWORD}'; GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO ${POSTGRES_NON_ROOT_USER}; GRANT CREATE ON SCHEMA public TO ${POSTGRES_NON_ROOT_USER}; EOSQL else echo "SETUP INFO: No Environment variables given!" fi SCRIPT ssh jared@15.204.247.153 "chmod +x ~/services/n8n/init-data.sh" ``` - [ ] **Step 9: Transfer volume tarballs to OVH** ```bash scp /tmp/n8n_db_storage.tar.gz jared@15.204.247.153:/tmp/ scp /tmp/n8n_n8n_storage.tar.gz jared@15.204.247.153:/tmp/ ``` Expected: both files appear in `/tmp/` on OVH - [ ] **Step 10: Create Docker volumes on OVH and import data** ```bash ssh jared@15.204.247.153 " docker volume create n8n_db_storage && docker volume create n8n_n8n_storage && docker run --rm -v n8n_db_storage:/data -v /tmp:/backup alpine sh -c 'tar xzf /backup/n8n_db_storage.tar.gz -C /data' && docker run --rm -v n8n_n8n_storage:/data -v /tmp:/backup alpine sh -c 'tar xzf /backup/n8n_n8n_storage.tar.gz -C /data' " ``` Expected: no errors - [ ] **Step 11: Start n8n stack on OVH** ```bash ssh jared@15.204.247.153 "cd ~/services/n8n && docker compose up -d" ``` Expected: postgres starts first (healthcheck passes), then n8n starts - [ ] **Step 12: Verify n8n is healthy on OVH** ```bash ssh jared@15.204.247.153 "docker compose -f ~/services/n8n/docker-compose.yml ps" ``` Expected: both containers show `Up` and postgres shows `(healthy)` ```bash ssh jared@15.204.247.153 "docker exec \$(docker ps -qf name=n8n-n8n) wget -qO- http://127.0.0.1:5678/healthz" ``` Expected: `{"status":"ok"}` or similar 200 response - [ ] **Step 13: Clean up temp files** ```bash ssh jared@15.204.247.153 "rm /tmp/n8n_db_storage.tar.gz /tmp/n8n_n8n_storage.tar.gz" rm /tmp/n8n_db_storage.tar.gz /tmp/n8n_n8n_storage.tar.gz ``` - [ ] **Step 14: Notify user — n8n ready for DNS cutover** Report: n8n production is running on OVH. User can now point `n8n.hyperthrive.io` to 15.204.247.153. After DNS propagates, stop source: `ssh hyperthrive-docker "cd ~/services/n8n && docker compose stop"` --- ## Task 2: Migrate n8n-test — testn8n.hyperthrive.io **Model tier:** Sonnet **Files on OVH:** - Create: `~/services/n8n-test/docker-compose.yml` - Create: `~/services/n8n-test/.env` - Create: `~/services/n8n-test/init-data.sh` **Volumes to migrate:** - `n8n-test_test_n8n_db_storage` → PostgreSQL data - `n8n-test_test_n8n_storage` → n8n app data and encryption key - [ ] **Step 1: Stop n8n-test stack on source** ```bash ssh hyperthrive-docker "cd ~/services/n8n-test && docker compose stop" ``` - [ ] **Step 2: Export postgres volume from source** ```bash ssh hyperthrive-docker "docker run --rm -v n8n-test_test_n8n_db_storage:/data alpine tar czf - -C /data ." > /tmp/n8n_test_db.tar.gz ``` - [ ] **Step 3: Export n8n-test app data volume from source** ```bash ssh hyperthrive-docker "docker run --rm -v n8n-test_test_n8n_storage:/data alpine tar czf - -C /data ." > /tmp/n8n_test_storage.tar.gz ``` - [ ] **Step 4: Restart n8n-test on source** ```bash ssh hyperthrive-docker "cd ~/services/n8n-test && docker compose start" ``` - [ ] **Step 5: Create service directory on OVH** ```bash ssh jared@15.204.247.153 "mkdir -p ~/services/n8n-test" ``` - [ ] **Step 6: Write docker-compose.yml on OVH** ```bash ssh jared@15.204.247.153 "cat > ~/services/n8n-test/docker-compose.yml" << 'COMPOSE' volumes: test_n8n_db_storage: test_n8n_storage: services: postgres: image: postgres:16 restart: always environment: - POSTGRES_USER - POSTGRES_PASSWORD - POSTGRES_DB - POSTGRES_NON_ROOT_USER - POSTGRES_NON_ROOT_PASSWORD volumes: - test_n8n_db_storage:/var/lib/postgresql/data - ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh healthcheck: test: ['CMD-SHELL', 'pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}'] interval: 5s timeout: 5s retries: 10 networks: - test_n8n_internal n8n: image: docker.n8n.io/n8nio/n8n:latest restart: always environment: - DB_TYPE=postgresdb - DB_POSTGRESDB_HOST=postgres - DB_POSTGRESDB_PORT=5432 - DB_POSTGRESDB_DATABASE=${POSTGRES_DB} - DB_POSTGRESDB_USER=${POSTGRES_NON_ROOT_USER} - DB_POSTGRESDB_PASSWORD=${POSTGRES_NON_ROOT_PASSWORD} - N8N_HOST=${N8N_HOST} - N8N_PORT=5678 - N8N_PROTOCOL=https - N8N_TRUSTED_PROXIES=1 - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true - N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true - EXECUTIONS_DATA_MAX_AGE=72 - NODE_ENV=production - WEBHOOK_URL=https://${N8N_HOST} - QUEUE_HEALTH_CHECK_ACTIVE=true healthcheck: test: ["CMD", "wget", "--no-check-certificate", "--spider", "http://127.0.0.1:5678/healthz"] interval: 12s timeout: 10s retries: 3 start_period: 15s volumes: - test_n8n_storage:/home/node/.n8n depends_on: postgres: condition: service_healthy networks: - test_n8n_internal - traefik labels: - "traefik.enable=true" - "traefik.http.routers.testn8n.rule=Host(`testn8n.hyperthrive.io`)" - "traefik.http.routers.testn8n.entrypoints=websecure" - "traefik.http.routers.testn8n.tls.certresolver=letsencrypt" - "traefik.http.routers.testn8n.service=testn8n" - "traefik.http.services.testn8n.loadbalancer.server.port=5678" - "traefik.docker.network=traefik" logging: driver: "json-file" options: max-size: "10m" max-file: "3" networks: test_n8n_internal: traefik: external: true COMPOSE ``` - [ ] **Step 7: Write .env on OVH** ```bash ssh jared@15.204.247.153 "cat > ~/services/n8n-test/.env" << 'ENV' POSTGRES_USER=n8n_test_user_pg POSTGRES_PASSWORD=l5t2xd8f%&RwB8aidK#2M POSTGRES_DB=n8n_test POSTGRES_NON_ROOT_USER=n8n_test_non_root_pg POSTGRES_NON_ROOT_PASSWORD=isqjKb5s0NWa&w*N@aGbO N8N_HOST=testn8n.hyperthrive.io N8N_VERSION=latest ENV ``` - [ ] **Step 8: Write init-data.sh on OVH** ```bash ssh jared@15.204.247.153 "cat > ~/services/n8n-test/init-data.sh" << 'SCRIPT' #!/bin/bash set -e; if [ -n "${POSTGRES_NON_ROOT_USER:-}" ] && [ -n "${POSTGRES_NON_ROOT_PASSWORD:-}" ]; then psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL CREATE USER ${POSTGRES_NON_ROOT_USER} WITH PASSWORD '${POSTGRES_NON_ROOT_PASSWORD}'; GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO ${POSTGRES_NON_ROOT_USER}; GRANT CREATE ON SCHEMA public TO ${POSTGRES_NON_ROOT_USER}; EOSQL else echo "SETUP INFO: No Environment variables given!" fi SCRIPT ssh jared@15.204.247.153 "chmod +x ~/services/n8n-test/init-data.sh" ``` - [ ] **Step 9: Transfer volume tarballs to OVH** ```bash scp /tmp/n8n_test_db.tar.gz jared@15.204.247.153:/tmp/ scp /tmp/n8n_test_storage.tar.gz jared@15.204.247.153:/tmp/ ``` - [ ] **Step 10: Create Docker volumes on OVH and import data** ```bash ssh jared@15.204.247.153 " docker volume create n8n-test_test_n8n_db_storage && docker volume create n8n-test_test_n8n_storage && docker run --rm -v n8n-test_test_n8n_db_storage:/data -v /tmp:/backup alpine sh -c 'tar xzf /backup/n8n_test_db.tar.gz -C /data' && docker run --rm -v n8n-test_test_n8n_storage:/data -v /tmp:/backup alpine sh -c 'tar xzf /backup/n8n_test_storage.tar.gz -C /data' " ``` - [ ] **Step 11: Start n8n-test stack on OVH** ```bash ssh jared@15.204.247.153 "cd ~/services/n8n-test && docker compose up -d" ``` - [ ] **Step 12: Verify n8n-test is healthy on OVH** ```bash ssh jared@15.204.247.153 "docker compose -f ~/services/n8n-test/docker-compose.yml ps" ``` Expected: both containers `Up`, postgres `(healthy)` ```bash ssh jared@15.204.247.153 "docker exec \$(docker ps -qf name=n8n-test-n8n) wget -qO- http://127.0.0.1:5678/healthz" ``` Expected: `{"status":"ok"}` - [ ] **Step 13: Clean up temp files** ```bash ssh jared@15.204.247.153 "rm /tmp/n8n_test_db.tar.gz /tmp/n8n_test_storage.tar.gz" rm /tmp/n8n_test_db.tar.gz /tmp/n8n_test_storage.tar.gz ``` - [ ] **Step 14: Notify user — n8n-test ready for DNS cutover** Report: n8n-test is running on OVH. User can now point `testn8n.hyperthrive.io` to 15.204.247.153. After DNS propagates, stop source: `ssh hyperthrive-docker "cd ~/services/n8n-test && docker compose stop"` --- ## Task 3: Migrate Formbricks — formbricks.hyperthrive.io **Model tier:** Sonnet **Files on OVH:** - Create: `~/services/formbricks/docker-compose.yml` - Create: `~/services/formbricks/.env` **Volumes to migrate:** - `formbricks_app_data` → Formbricks application data (mounted at `/app/data`) - `formbricks_postgres_data` → PostgreSQL data (pgvector) - Anonymous uploads volume → mounted at `/home/nextjs/apps/web/uploads` in the running container (may contain user-uploaded files; check size before deciding whether to migrate) - [ ] **Step 1: Check if the anonymous uploads volume has data worth migrating** ```bash ssh hyperthrive-docker "docker exec formbricks-formbricks-1 du -sh /home/nextjs/apps/web/uploads 2>/dev/null || echo 'path not found or empty'" ``` If output shows significant data (> a few KB), capture the volume hash for Step 3b. If empty or not found, skip Step 3b. - [ ] **Step 2: Stop formbricks stack on source** ```bash ssh hyperthrive-docker "cd ~/services/formbricks && docker compose stop" ``` - [ ] **Step 3: Export formbricks app data volume** ```bash ssh hyperthrive-docker "docker run --rm -v formbricks_app_data:/data alpine tar czf - -C /data ." > /tmp/formbricks_app_data.tar.gz ``` - [ ] **Step 3b (conditional): Export anonymous uploads volume if it had data** Get the volume hash from the docker inspect output: `4a21529d8e041b3ad60aa6cd2b821788c4a26a1245696ea90df324fe4a1f7e7a` ```bash ssh hyperthrive-docker "docker run --rm -v 4a21529d8e041b3ad60aa6cd2b821788c4a26a1245696ea90df324fe4a1f7e7a:/data alpine tar czf - -C /data ." > /tmp/formbricks_uploads.tar.gz ``` Skip this step if Step 1 showed the directory was empty. - [ ] **Step 4: Export postgres volume** ```bash ssh hyperthrive-docker "docker run --rm -v formbricks_postgres_data:/data alpine tar czf - -C /data ." > /tmp/formbricks_postgres_data.tar.gz ``` - [ ] **Step 5: Restart formbricks on source** ```bash ssh hyperthrive-docker "cd ~/services/formbricks && docker compose start" ``` - [ ] **Step 6: Create service directory on OVH** ```bash ssh jared@15.204.247.153 "mkdir -p ~/services/formbricks" ``` - [ ] **Step 7: Write docker-compose.yml on OVH** ```bash ssh jared@15.204.247.153 "cat > ~/services/formbricks/docker-compose.yml" << 'COMPOSE' services: formbricks: image: formbricks/formbricks restart: always environment: NEXTAUTH_SECRET: ${NEXTAUTH_SECRET} ENCRYPTION_KEY: ${ENCRYPTION_KEY} CRON_SECRET: ${CRON_SECRET} WEBAPP_URL: ${WEBAPP_URL} NEXTAUTH_URL: ${WEBAPP_URL} SHORT_URL_BASE: ${WEBAPP_URL} DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/formbricks PORT: 3000 EMAIL_VERIFICATION_DISABLED: 1 volumes: - formbricks_app_data:/app/data depends_on: postgres: condition: service_healthy networks: - internal - traefik labels: - "traefik.enable=true" - "traefik.http.routers.formbricks.rule=Host(`formbricks.hyperthrive.io`)" - "traefik.http.routers.formbricks.entrypoints=websecure" - "traefik.http.routers.formbricks.tls.certresolver=letsencrypt" - "traefik.http.services.formbricks.loadbalancer.server.port=3000" - "traefik.docker.network=traefik" postgres: image: ankane/pgvector:v0.5.1 restart: always environment: POSTGRES_DB: formbricks POSTGRES_USER: postgres POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - formbricks_postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5 networks: - internal volumes: formbricks_app_data: name: formbricks_app_data formbricks_postgres_data: name: formbricks_postgres_data networks: internal: name: formbricks_internal traefik: external: true COMPOSE ``` - [ ] **Step 8: Write .env on OVH** ```bash ssh jared@15.204.247.153 "cat > ~/services/formbricks/.env" << 'ENV' NEXTAUTH_SECRET=bc366a86606242b11f88d107eecebe4cc83ff421d41d06142d191581f165dc46 ENCRYPTION_KEY=ed8c30d07f82183b0dd9f5a21df1fb5720072768c501e6631531ae73cca9e90e CRON_SECRET=349af37fef8ccb87377c083c5a2783e45becf04668614a110de127f32ce2c612 POSTGRES_PASSWORD=9d9eXgtFn6gogV0fVWk7BSAF1MJjcFuwfU966sNVE0 WEBAPP_URL=https://formbricks.hyperthrive.io ENV ``` - [ ] **Step 9: Transfer volume tarballs to OVH** ```bash scp /tmp/formbricks_app_data.tar.gz jared@15.204.247.153:/tmp/ scp /tmp/formbricks_postgres_data.tar.gz jared@15.204.247.153:/tmp/ # If uploads were exported in Step 3b: # scp /tmp/formbricks_uploads.tar.gz jared@15.204.247.153:/tmp/ ``` - [ ] **Step 10: Create Docker volumes on OVH and import data** ```bash ssh jared@15.204.247.153 " docker volume create formbricks_app_data && docker volume create formbricks_postgres_data && docker run --rm -v formbricks_app_data:/data -v /tmp:/backup alpine sh -c 'tar xzf /backup/formbricks_app_data.tar.gz -C /data' && docker run --rm -v formbricks_postgres_data:/data -v /tmp:/backup alpine sh -c 'tar xzf /backup/formbricks_postgres_data.tar.gz -C /data' " ``` - [ ] **Step 11: Start formbricks stack on OVH** ```bash ssh jared@15.204.247.153 "cd ~/services/formbricks && docker compose up -d" ``` Expected: postgres starts first (healthcheck), then formbricks - [ ] **Step 12: Verify formbricks is healthy on OVH** ```bash ssh jared@15.204.247.153 "docker compose -f ~/services/formbricks/docker-compose.yml ps" ``` Expected: both containers `Up`, postgres `(healthy)` ```bash ssh jared@15.204.247.153 "curl -sf http://localhost:3000/health 2>/dev/null || docker logs \$(docker ps -qf name=formbricks-formbricks) --tail 20" ``` Expected: 200 response or logs showing server started on port 3000 - [ ] **Step 13: Clean up temp files** ```bash ssh jared@15.204.247.153 "rm /tmp/formbricks_app_data.tar.gz /tmp/formbricks_postgres_data.tar.gz 2>/dev/null; true" rm /tmp/formbricks_app_data.tar.gz /tmp/formbricks_postgres_data.tar.gz 2>/dev/null; true ``` - [ ] **Step 14: Notify user — formbricks ready for DNS cutover** Report: formbricks is running on OVH. User can now point `formbricks.hyperthrive.io` to 15.204.247.153. After DNS propagates, stop source: `ssh hyperthrive-docker "cd ~/services/formbricks && docker compose stop"` --- ## Post-Migration Checklist (after all DNS cutovers) - [ ] Confirm each domain resolves to 15.204.247.153: `dig n8n.hyperthrive.io`, `dig testn8n.hyperthrive.io`, `dig formbricks.hyperthrive.io` - [ ] Confirm HTTPS works and certs are valid for all three domains - [ ] Log into n8n at https://n8n.hyperthrive.io and verify workflows and credentials are intact - [ ] Log into formbricks at https://formbricks.hyperthrive.io and verify surveys/data are present - [ ] Stop all three services on hyperthrive-docker once verified - [ ] Update `docs/services-inventory.md` on OVH with the three new services