server-ovh-prod-01-main/docs/plans/2026-03-18-caprover-migrati...

569 lines
15 KiB
Markdown

# CapRover Migration Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Migrate Vaultwarden, n8n, Forgejo, and NocoDB from CapRover to the OVH production server, plus add Watchtower for automatic container updates.
**Architecture:** Each service is an independent Docker Compose stack under `~/services/SERVICE_NAME/` on the OVH server (15.204.247.153). Traefik handles SSL termination and routing. The existing `~/services/backup.sh` auto-discovers all services — no backup configuration needed per service. Services are deployed in two batches: Watchtower first, then Vaultwarden + n8n in parallel, then Forgejo + NocoDB in parallel.
**Tech Stack:** Docker Compose, Traefik v3.0, Let's Encrypt, nicholas-fedor/watchtower, vaultwarden/server, n8nio/n8n, codeberg.org/forgejo/forgejo, nocodb/nocodb
---
## Batch 0: Watchtower
### Task 1: Create Watchtower compose file
**Files:**
- Create: `configs/watchtower/docker-compose.yml` (in this repo, for reference)
**Step 1: Create the compose file locally**
```yaml
# configs/watchtower/docker-compose.yml
services:
watchtower:
image: nicholas-fedor/watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_SCHEDULE=0 0 3 * * *
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_INCLUDE_STOPPED=false
- WATCHTOWER_NOTIFICATIONS_LEVEL=warn
command: --schedule "0 0 3 * * *"
```
Note: No networks needed — Watchtower talks to the Docker daemon directly via the socket. Runs at 3 AM daily. `WATCHTOWER_CLEANUP=true` removes old images after updating.
**Step 2: Commit the config**
```bash
git add configs/watchtower/docker-compose.yml
git commit -m "chore: add watchtower compose config"
```
---
### Task 2: Deploy Watchtower on OVH
**Step 1: Create service directory on remote server**
```bash
ssh jared@15.204.247.153 "mkdir -p ~/services/watchtower"
```
**Step 2: Copy compose file to server**
```bash
scp configs/watchtower/docker-compose.yml jared@15.204.247.153:~/services/watchtower/
```
**Step 3: Start Watchtower**
```bash
ssh jared@15.204.247.153 "cd ~/services/watchtower && docker compose up -d"
```
**Step 4: Verify it's running**
```bash
ssh jared@15.204.247.153 "docker ps | grep watchtower"
```
Expected: one `watchtower` container in `Up` state.
**Step 5: Check logs to confirm it's watching containers**
```bash
ssh jared@15.204.247.153 "docker logs watchtower --tail 20"
```
Expected: logs showing it has found and is monitoring existing containers.
---
## Batch 1: Vaultwarden + n8n (deploy in parallel)
### Task 3: Create Vaultwarden compose file
**Files:**
- Create: `configs/vaultwarden/docker-compose.yml`
**Step 1: Create the compose file**
```yaml
# configs/vaultwarden/docker-compose.yml
services:
vaultwarden:
image: vaultwarden/server:latest
restart: unless-stopped
volumes:
- ./data:/data
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.vaultwarden.rule=Host(`bitwarden.swansoncloud.com`)"
- "traefik.http.routers.vaultwarden.entrypoints=websecure"
- "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt"
- "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
- "traefik.docker.network=traefik"
networks:
traefik:
external: true
```
Note: Modern Vaultwarden (post-1.25) handles WebSocket on port 80 — no separate router needed for `/notifications/hub`.
**Step 2: Commit**
```bash
git add configs/vaultwarden/docker-compose.yml
git commit -m "chore: add vaultwarden compose config"
```
---
### Task 4: Migrate Vaultwarden data from CapRover
**Step 1: SSH into the CapRover server and locate the Vaultwarden data directory**
The CapRover persistent data path is typically at:
`/var/lib/docker/volumes/captain--vaultwarden--1-data/_data/`
SSH into CapRover and confirm:
```bash
# On CapRover server
ls /var/lib/docker/volumes/ | grep vaultwarden
ls /var/lib/docker/volumes/captain--vaultwarden--1-data/_data/
```
Expected contents: `db.sqlite3`, `config.json`, `attachments/`, `sends/`
**Step 2: Copy data dir from CapRover to OVH**
Create the destination directory on OVH first:
```bash
ssh jared@15.204.247.153 "mkdir -p ~/services/vaultwarden/data"
```
Then from CapRover server (or via local machine as relay):
```bash
# Option A: If you can SSH from CapRover to OVH directly
scp -r /var/lib/docker/volumes/captain--vaultwarden--1-data/_data/* jared@15.204.247.153:~/services/vaultwarden/data/
# Option B: Via your local machine (two-hop)
# First download from CapRover to local:
scp -r root@CAPROVER_IP:/var/lib/docker/volumes/captain--vaultwarden--1-data/_data/ ./vaultwarden-backup/
# Then upload to OVH:
scp -r ./vaultwarden-backup/ jared@15.204.247.153:~/services/vaultwarden/data/
```
**Step 3: Verify files landed on OVH**
```bash
ssh jared@15.204.247.153 "ls -la ~/services/vaultwarden/data/"
```
Expected: `db.sqlite3` present, `config.json` present.
---
### Task 5: Deploy Vaultwarden on OVH
**Step 1: Copy compose file to server**
```bash
scp configs/vaultwarden/docker-compose.yml jared@15.204.247.153:~/services/vaultwarden/
```
**Step 2: Start Vaultwarden**
```bash
ssh jared@15.204.247.153 "cd ~/services/vaultwarden && docker compose up -d"
```
**Step 3: Verify container is running**
```bash
ssh jared@15.204.247.153 "docker ps | grep vaultwarden"
```
**Step 4: Test via temporary access (before DNS cutover)**
Access via direct IP to confirm Traefik is routing it (will show SSL warning, that's fine):
```
http://15.204.247.153
```
Or check Traefik dashboard at https://traefik.hyperthrive.app to confirm the `vaultwarden` router appears.
**Step 5: Check logs for errors**
```bash
ssh jared@15.204.247.153 "docker logs vaultwarden-vaultwarden-1 --tail 30"
```
Expected: No errors. Should see startup messages and database initialization.
---
### Task 6: Cut DNS for Vaultwarden + verify
**Step 1: Update DNS**
In your DNS provider, update `bitwarden.swansoncloud.com` A record to point to `15.204.247.153`.
**Step 2: Wait for DNS propagation (~2-5 minutes), then test**
```bash
curl -I https://bitwarden.swansoncloud.com
```
Expected: HTTP 200 response.
**Step 3: Verify vault unlocks**
Open https://bitwarden.swansoncloud.com in browser — confirm you can log in and your vault data is present.
**Step 4: Test browser extension**
Re-connect your Bitwarden browser extension to the new server URL and confirm it syncs.
---
### Task 7: Create n8n compose file
**Files:**
- Create: `configs/n8n-swanson/docker-compose.yml`
**Step 1: Check existing n8n instances on OVH for pattern consistency**
```bash
ssh jared@15.204.247.153 "cat ~/services/n8n*/docker-compose.yml 2>/dev/null || ls ~/services/ | grep n8n"
```
Adapt the compose below to match any conventions found.
**Step 2: Create the compose file**
```yaml
# configs/n8n-swanson/docker-compose.yml
services:
n8n:
image: n8nio/n8n
restart: unless-stopped
environment:
- N8N_HOST=n8n.swansoncloud.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.swansoncloud.com/
- N8N_SECURE_COOKIE=false
- TZ=America/Chicago
volumes:
- ./data:/home/node/.n8n
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n-swanson.rule=Host(`n8n.swansoncloud.com`)"
- "traefik.http.routers.n8n-swanson.entrypoints=websecure"
- "traefik.http.routers.n8n-swanson.tls.certresolver=letsencrypt"
- "traefik.http.services.n8n-swanson.loadbalancer.server.port=5678"
- "traefik.docker.network=traefik"
networks:
traefik:
external: true
```
Note: Router name is `n8n-swanson` (not just `n8n`) to avoid collision with existing n8n instances on this server.
**Step 3: Commit**
```bash
git add configs/n8n-swanson/docker-compose.yml
git commit -m "chore: add n8n swansoncloud compose config"
```
---
### Task 8: Deploy n8n on OVH
**Step 1: Create service directory and deploy**
```bash
ssh jared@15.204.247.153 "mkdir -p ~/services/n8n-swanson"
scp configs/n8n-swanson/docker-compose.yml jared@15.204.247.153:~/services/n8n-swanson/
ssh jared@15.204.247.153 "cd ~/services/n8n-swanson && docker compose up -d"
```
**Step 2: Verify container is running**
```bash
ssh jared@15.204.247.153 "docker ps | grep n8n-swanson"
```
**Step 3: Cut DNS**
Update `n8n.swansoncloud.com` A record → `15.204.247.153`.
**Step 4: Verify n8n is accessible**
```bash
curl -I https://n8n.swansoncloud.com
```
Expected: HTTP 200. Open in browser and complete initial setup (create admin account).
---
## Batch 2: Forgejo + NocoDB (deploy in parallel)
### Task 9: Create Forgejo compose file
**Files:**
- Create: `configs/forgejo/docker-compose.yml`
**Step 1: Create the compose file**
```yaml
# configs/forgejo/docker-compose.yml
services:
forgejo:
image: codeberg.org/forgejo/forgejo:latest
restart: unless-stopped
environment:
- USER_UID=1000
- USER_GID=1000
volumes:
- ./data:/data
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.forgejo.rule=Host(`forgejo.swansoncloud.com`)"
- "traefik.http.routers.forgejo.entrypoints=websecure"
- "traefik.http.routers.forgejo.tls.certresolver=letsencrypt"
- "traefik.http.services.forgejo.loadbalancer.server.port=3000"
- "traefik.docker.network=traefik"
networks:
traefik:
external: true
```
Note: SQLite is the default and sufficient for 2 repos. No external database needed. SSH git access is omitted — HTTPS is fine for personal use.
**Step 2: Commit**
```bash
git add configs/forgejo/docker-compose.yml
git commit -m "chore: add forgejo compose config"
```
---
### Task 10: Deploy Forgejo on OVH
**Step 1: Create service directory and deploy**
```bash
ssh jared@15.204.247.153 "mkdir -p ~/services/forgejo"
scp configs/forgejo/docker-compose.yml jared@15.204.247.153:~/services/forgejo/
ssh jared@15.204.247.153 "cd ~/services/forgejo && docker compose up -d"
```
**Step 2: Verify container is running**
```bash
ssh jared@15.204.247.153 "docker ps | grep forgejo"
```
**Step 3: Cut DNS**
Update `forgejo.swansoncloud.com` A record → `15.204.247.153`.
**Step 4: Access web UI and complete initial setup**
Open https://forgejo.swansoncloud.com — Forgejo will show a first-run setup page. Configure:
- Database: SQLite (default)
- Site URL: `https://forgejo.swansoncloud.com`
- Admin username/password
---
### Task 11: Migrate Gitea repos to Forgejo
**Step 1: In Forgejo web UI, use the built-in migration tool**
Go to: https://forgejo.swansoncloud.com → `+` (New) → `Migrate Repository`
Select: **Gitea** as the source type.
**Step 2: For each repo at gitea.swansoncloud.com/explore/repos**
Fill in:
- Clone URL: `https://gitea.swansoncloud.com/USERNAME/REPO_NAME.git`
- If Gitea requires auth, provide credentials
Forgejo will pull the full git history, issues, and wiki (if any).
Repeat for all repos (there are 2).
**Step 3: Verify repos migrated**
Confirm both repos appear at https://forgejo.swansoncloud.com with full commit history.
**Step 4: Update any local git remotes**
On your local machine, update remotes from `gitea.swansoncloud.com``forgejo.swansoncloud.com`:
```bash
git remote set-url origin https://forgejo.swansoncloud.com/USERNAME/REPO_NAME.git
```
---
### Task 12: Create NocoDB compose file
**Files:**
- Create: `configs/nocodb/docker-compose.yml`
**Step 1: Create the compose file**
```yaml
# configs/nocodb/docker-compose.yml
services:
nocodb:
image: nocodb/nocodb:latest
restart: unless-stopped
volumes:
- ./data:/usr/app/data
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.nocodb.rule=Host(`nocodb.swansoncloud.com`)"
- "traefik.http.routers.nocodb.entrypoints=websecure"
- "traefik.http.routers.nocodb.tls.certresolver=letsencrypt"
- "traefik.http.services.nocodb.loadbalancer.server.port=8080"
- "traefik.docker.network=traefik"
networks:
traefik:
external: true
```
**Step 2: Commit**
```bash
git add configs/nocodb/docker-compose.yml
git commit -m "chore: add nocodb compose config"
```
---
### Task 13: Deploy NocoDB on OVH
**Step 1: Create service directory and deploy**
```bash
ssh jared@15.204.247.153 "mkdir -p ~/services/nocodb"
scp configs/nocodb/docker-compose.yml jared@15.204.247.153:~/services/nocodb/
ssh jared@15.204.247.153 "cd ~/services/nocodb && docker compose up -d"
```
**Step 2: Verify container is running**
```bash
ssh jared@15.204.247.153 "docker ps | grep nocodb"
```
**Step 3: Cut DNS**
Update `nocodb.swansoncloud.com` A record → `15.204.247.153`.
**Step 4: Verify NocoDB is accessible and complete setup**
Open https://nocodb.swansoncloud.com — create admin account on first run.
---
## Batch 3: Verification & Cleanup
### Task 14: Verify backup coverage for all new services
**Step 1: Run backup script manually**
```bash
ssh jared@15.204.247.153 "~/services/backup.sh"
```
**Step 2: Verify all 4 new services are discovered**
```bash
ssh jared@15.204.247.153 "tar -tzf ~/backups/current/latest.tar.gz | grep -E 'vaultwarden|n8n-swanson|forgejo|nocodb'"
```
Expected: entries for all four services.
---
### Task 15: Update services inventory doc
**Files:**
- Modify: `docs/services-inventory.md`
Add entries for all 4 new services + Watchtower. Follow existing format in the file. Include:
- Service name
- Domain
- Container name(s)
- Image
- Data path on server
- Deployment date (today: 2026-03-18)
- Status: ✅ LIVE
**Commit:**
```bash
git add docs/services-inventory.md
git commit -m "docs: update services inventory with migrated services"
```
---
### Task 16: Decommission CapRover services
Once all DNS records are confirmed healthy and pointing to OVH:
**Step 1: Confirm all 4 services are live on OVH**
```bash
curl -I https://bitwarden.swansoncloud.com
curl -I https://n8n.swansoncloud.com
curl -I https://forgejo.swansoncloud.com
curl -I https://nocodb.swansoncloud.com
```
All should return HTTP 200.
**Step 2: Stop services on CapRover**
In the CapRover dashboard, stop (do not delete yet) Vaultwarden, n8n, Gitea, and NocoDB. Leave them stopped for 48 hours as a rollback window.
**Step 3: After 48 hours, delete CapRover apps and decommission server**
Once confident everything is working, delete the CapRover apps and shut down the CapRover server.
---
## Post-Migration Reminders
These tasks are tracked separately and should be done after all services are live:
1. **Fireflies.ai automation** — Set up n8n workflow to retrieve meeting transcripts from Fireflies.ai and back them up. (See task tracker)
2. **hyperthrive.io contact form** — Set up n8n workflow for contact form submissions on hyperthrive.io. (See task tracker)