server-ovh-prod-01-main/docs/superpowers/plans/2026-05-14-migrate-n8n-form...

21 KiB

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
ssh hyperthrive-docker "cd ~/services/n8n && docker compose stop"

Expected: containers stop, no error

  • Step 2: Export postgres volume from source
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
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)
ssh hyperthrive-docker "cd ~/services/n8n && docker compose start"

Expected: containers running again

  • Step 5: Create service directory on OVH
ssh jared@15.204.247.153 "mkdir -p ~/services/n8n"
  • Step 6: Write docker-compose.yml on OVH
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
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
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
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
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
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
ssh jared@15.204.247.153 "docker compose -f ~/services/n8n/docker-compose.yml ps"

Expected: both containers show Up and postgres shows (healthy)

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
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

ssh hyperthrive-docker "cd ~/services/n8n-test && docker compose stop"
  • Step 2: Export postgres volume from source
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
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
ssh hyperthrive-docker "cd ~/services/n8n-test && docker compose start"
  • Step 5: Create service directory on OVH
ssh jared@15.204.247.153 "mkdir -p ~/services/n8n-test"
  • Step 6: Write docker-compose.yml on OVH
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
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
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
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
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
ssh jared@15.204.247.153 "cd ~/services/n8n-test && docker compose up -d"
  • Step 12: Verify n8n-test is healthy on OVH
ssh jared@15.204.247.153 "docker compose -f ~/services/n8n-test/docker-compose.yml ps"

Expected: both containers Up, postgres (healthy)

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
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

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
ssh hyperthrive-docker "cd ~/services/formbricks && docker compose stop"
  • Step 3: Export formbricks app data volume
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

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
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
ssh hyperthrive-docker "cd ~/services/formbricks && docker compose start"
  • Step 6: Create service directory on OVH
ssh jared@15.204.247.153 "mkdir -p ~/services/formbricks"
  • Step 7: Write docker-compose.yml on OVH
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
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
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
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
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
ssh jared@15.204.247.153 "docker compose -f ~/services/formbricks/docker-compose.yml ps"

Expected: both containers Up, postgres (healthy)

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
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