n8n Migration Runbook — Railway → R940 (Tier 1)¶
Status: Tier 1 COMPLETE (2026-08-25). n8n production: https://n8n.last1.enterprises/ (R940 VM 112). Railway n8n service deleted 2026-08-24 (W10).
Source: disciplined-beauty / production / service n8n on Railway.
Target: VM n8n-r940 (Proxmox VMID 112) on pve-r940.
URLs: n8n.theonset.app (Railway, fallback during soak) → n8n.last1.enterprises (primary after cutover).
Confirmed live state (Ryan, 2026-08-24)¶
| Item | Value |
|---|---|
| Workflows | 3 (2 published, all 3 active) |
| Schedules | 2 daily (disable on one instance during parallel test) |
| Purpose | Onset social media marketing automation |
| User | Solo (Ryan) |
| Criticality | Non-critical; brief downtime OK |
| Backing store | SQLite under /data/.n8n (Railway + R940) |
W1B — Railway / live audit¶
Verified without Railway API¶
| Item | Finding |
|---|---|
| Public URL | https://n8n.theonset.app → Cloudflare proxied CNAME → o9lqbgq5.up.railway.app |
| Health | /healthz returns {"status":"ok"} |
| Auth model | Email login (/rest/settings: authenticationMethod: email, setup complete) |
| Repo deploy | n8nio/n8n + start.sh → /data/.n8n, SQLite |
| Hidden git deps | No n8n webhook URLs in Last-1-Enterprises repos |
| Railway direct host | o9lqbgq5.up.railway.app returns 404 (custom domain only) |
Railway service (still unverified)¶
No N8N_RAILWAY_PROD_API_TOKEN in BWS last1-platform yet. Railway compute metadata (memory/replicas/volume) remains unknown. Workflows live in n8n, not Railway directly, so migration proceeded via n8n API.
Workflow inventory (via n8n API, 2026-08-24)¶
| Workflow | Active (source) | Trigger |
|---|---|---|
| Workflow 1: Onset Content Generator | yes | Weekly, Monday 06:00 PT |
| Workflow 2: Onset Daily Publisher | yes | Daily 09:00 PT |
| Workflow 3: Onset Manual Post | no | Manual only |
| Onset Content Generator (duplicate) | no | Weekly (legacy copy) |
Ryan confirmed 3 operational workflows; the fourth is an inactive duplicate on source (not imported to R940).
Credential inventory (names only; values encrypted on source)¶
| Credential | Type | Used for |
|---|---|---|
| Bearer Auth account | httpBearerAuth |
Supabase upload + social scheduler GraphQL (createPost with instagram metadata) |
| Header Auth account | httpHeaderAuth |
HTTP integrations (×2) |
| Google Sheets account | googleSheetsOAuth2Api |
Content queue / status sheet |
| Anthropic account | anthropicApi |
Content generation |
Ryan action (W6): Re-enter credentials in R940 n8n UI. Instagram posting is via the scheduler GraphQL node (not a native Instagram OAuth node). Google Sheets OAuth may need re-authorization.
BWS keys added (last1-platform)¶
| Key | Purpose |
|---|---|
N8N_R940_PROD_ENCRYPTION_KEY |
Fresh R940 credential encryption |
N8N_R940_PROD_BASIC_AUTH_USER / _PASSWORD |
Reverse-proxy basic auth |
N8N_R940_PROD_OWNER_EMAIL |
n8n owner login email (ryan@last1.enterprises) |
N8N_R940_PROD_OWNER_PASSWORD |
n8n owner login password |
N8N_R940_PROD_LICENSE_KEY |
n8n license activation key (also N8N_LICENSE_ACTIVATION_KEY in /etc/n8n/env) |
N8N_RAILWAY_PROD_API_KEY |
Source n8n public API (migration) |
N8N_RAILWAY_PROD_MCP_TOKEN |
Source MCP access (keep on source if desired) |
W2 — R940 VM provisioning (DONE)¶
| Property | Value |
|---|---|
| Hostname | n8n-r940 |
| VMID | 112 |
| LAN IP | 192.168.1.94/24 |
| Tailscale | n8n-r940.tailc3bbdc.ts.net (100.99.141.113) |
| Spec | 2 vCPU, 4 GB RAM |
| Boot disk | 50 GB tank-fast (Noble cloud image) |
| Data disk | 20 GB tank-fast → /data (n8n SQLite + files) |
| OS | Ubuntu 24.04 LTS |
| Hardening | fail2ban, ufw (SSH + n8n from LAN/Tailscale only) |
Cloud-init note: attach ide2 cloudinit before first boot on future VMs; VM 112 required offline SSH key injection on first bring-up.
W3 — n8n installation (DONE)¶
| Component | Path / detail |
|---|---|
| Docker stack | /opt/n8n-r940/ (Dockerfile, start.sh, docker-compose.yml) |
| Data | /data/.n8n → container /home/node/.n8n |
| systemd | n8n-r940.service |
| Env file | /etc/n8n/env (mode 640, root:ryan) |
| Image | n8nio/n8n:latest via local build |
| Metrics | N8N_METRICS=true |
| Listen | :5678 (ufw: LAN + Tailscale only) |
Verify:
W4 — BWS secrets (PARTIAL)¶
Created in last1-platform project (no dedicated n8n BWS project visible to automation token):
| Key | Status |
|---|---|
N8N_R940_PROD_ENCRYPTION_KEY |
Created (fresh; do not reuse Railway key) |
N8N_R940_PROD_BASIC_AUTH_USER |
Created |
N8N_R940_PROD_BASIC_AUTH_PASSWORD |
Created |
N8N_RAILWAY_PROD_API_TOKEN |
Not found — Ryan to confirm key name/project |
Social media creds (N8N_R940_PROD_*) |
Pending Railway env audit |
Runtime mapping on VM: /etc/n8n/env (manual restore script TBD; mirror LastVet restore-*-from-bws.sh pattern).
W5 — Workflow migration (DONE)¶
Exported from n8n.theonset.app via public API (requires browser User-Agent; Cloudflare blocks raw API clients).
Imported to R940 with all schedules disabled (active: false):
| Workflow | R940 status |
|---|---|
| Workflow 1: Onset Content Generator | imported, inactive |
| Workflow 2: Onset Daily Publisher | imported, inactive |
| Workflow 3: Onset Manual Post | imported, inactive |
Credential references stripped during import (new encryption key). Ryan must re-add in R940 UI:
- Bearer Auth account (social scheduler / Instagram GraphQL)
- Header Auth accounts (×2)
- Google Sheets OAuth
- Anthropic API
SQLite persists at /data/.n8n/.n8n/database.sqlite on the 20 GB data disk (fixed start.sh to run as node user with N8N_USER_FOLDER=/data/.n8n).
W6 — Parallel test (DONE 2026-08-24)¶
Ryan confirmed all workflow tests pass (Anthropic, Supabase bearer, Google Sheets OAuth with Drive + Sheets APIs enabled on GCP project 115729370665).
W6 archive — parallel test notes¶
Access URL (parallel period): http://n8n-r940.tailc3bbdc.ts.net:5678/ (HTTP on Tailscale, port required).
During parallel testing, R940 uses N8N_PROTOCOL=http and N8N_SECURE_COOKIE=false because TLS is not yet terminated on this host. Without that, n8n shows the “secure cookie” error when visiting via http://…:5678. At W7 cutover, switch to HTTPS on n8n.last1.enterprises and re-enable secure cookies.
Manual trigger each workflow on R940 only. Verify social posts. Ryan approves cutover.
W7 — Cutover (DONE 2026-08-24)¶
Executed:
- Railway schedules disabled — Workflow 1 + 2 deactivated on
n8n.theonset.app. - R940 schedules enabled — Workflow 1 + 2 active on VM 112.
- Ingress —
cloudflaredonlastvet-edge(192.168.1.89):n8n.last1.enterprises→http://192.168.1.94:5678. - DNS — Cloudflare CNAME
n8n.last1.enterprises→{tunnel-id}.cfargotunnel.com(proxied). Removed mistakenn8n.last1.enterprises.last.vetrecord. - n8n env —
/etc/n8n/envupdated:N8N_HOST=n8n.last1.enterprises,N8N_PROTOCOL=https, secure cookies on, webhook/editor URLs onhttps://n8n.last1.enterprises/. - Verify —
curl https://n8n.last1.enterprises/healthz→{"status":"ok"}.
Primary URL: https://n8n.last1.enterprises/
Legacy URL: n8n.theonset.app DNS deleted at W9.2 (2026-08-25). Railway compute deleted at W10 (2026-08-24).
Post-cutover: Add Google OAuth redirect URI https://n8n.last1.enterprises/rest/oauth2-credential/callback and reconnect Google Sheets credential if sheet nodes fail after hostname change.
W8 — Soak (SKIPPED 2026-08-25)¶
Ryan explicit decision: skip 24–48h soak on non-critical Tier 1 warm-up. No Railway fallback after W9/W10; Tuesday 09:00 PT Daily Publisher is first production proof on R940 alone.
Document as founder's call for Tier 1 only. Higher tiers retain recommended soak (see consolidation plan).
W9 — DNS retirement + archive (DONE 2026-08-25)¶
W9.1 Final Railway data capture¶
Archive: lastvet-gates/backups/n8n-railway-teardown-2026-08-25/
| Artifact | Status |
|---|---|
| 4 workflow JSON exports (full) | Done via n8n public API |
| Credentials summary (names/types) | Done |
| Recent executions metadata | Done |
Railway SQLite volume (/data/.n8n) |
Not captured — requires RAILWAY_TOKEN + railway volume or service shell; workflow JSON is restoration path |
W9.2 DNS¶
- Action: Deleted Cloudflare record
n8n.theonset.app→o9lqbgq5.up.railway.app(proxied CNAME). Ryan chose delete, not CNAME ton8n.last1.enterprises.
W10 — Railway teardown (DONE 2026-08-24T18:54:03Z)¶
| Step | Status |
|---|---|
Delete n8n service in disciplined-beauty / production |
Done — service id 8fbf94ec-b660-4950-935a-e848366ea727 |
| Delete attached volume | Done — removed with service delete |
| Verify service gone | Done — not listed in project services; Railway hostname returns 404 |
| Cost verification next cycle | Expect ~$5–20/mo reduction; confirm on next disciplined-beauty invoice |
Method: Railway GraphQL serviceDelete (CLI railway whoami failed with same token; GraphQL API worked). Store token as LAST1_PLATFORM_RAILWAY_PROD_API_TOKEN in BWS for Tier 2+ teardown scripts.
W11 — Observability (DONE 2026-08-25)¶
Deployed to VM 111 (192.168.1.93) and VM 112:
| Component | Detail |
|---|---|
node_exporter |
192.168.1.94:9100 |
| Promtail → Loki | host=n8n-r940, journal → 192.168.1.93:3100 |
Prometheus job=n8n |
192.168.1.94:5678/metrics (up) |
| Grafana dashboard | n8n R940 (uid=n8n-r940-prod) — http://observability.tailc3bbdc.ts.net:3000 |
| Prometheus alerts | N8nHostDown, N8nMetricsDown (critical); N8nActiveWorkflowsLow, N8nHighMemory (warning) |
| Loki ruler | N8nWorkflowExecutionFailed, N8nServiceSilent (warning) |
Deploy script: scripts/observability/deploy-n8n-observability.sh
Alert test (2026-08-24): Stopped node_exporter on n8n-r940 for ~4m. N8nHostDown transitioned pending → firing at 3m (for: 3m rule). Cleared within 15s of restart. https://n8n.last1.enterprises/healthz stayed OK (n8n container unaffected). Pager delivery not independently verified in this session; confirm Ryan received critical route if expected.
Rollback (post-W9)¶
No Railway rollback. Archive at backups/n8n-railway-teardown-2026-08-25/ + R940 vzdump if VM failure.
Lessons learned (Tier 1 + template for Tier 2–6)¶
- Workflow audit beats manual count: Ryan said 3 workflows; API found 4 (inactive duplicate Content Generator). Always export via API before cutover.
- Cloudflare + n8n API: Public API requires browser User-Agent or Cloudflare returns 1010.
- Credential re-entry: Fresh
N8N_R940_PROD_ENCRYPTION_KEYmeans credentials cannot be copied encrypted; re-enter API keys + Google OAuth reconnect. Enable Google Drive API and Google Sheets API on the OAuth GCP project (n8n uses Drive to list spreadsheets). - Ingress pattern:
cloudflaredonlastvet-edgeroutesn8n.last1.enterprises→ LAN192.168.1.94:5678; DNS CNAME to{tunnel-id}.cfargotunnel.com. - BWS naming:
N8N_R940_PROD_*underlast1-platformproject works; storeN8N_R940_PROD_API_KEYafter UI rotation. - Soak skip (Tier 1 only): Ryan accepted real risk on non-critical Onset marketing automation. Not a template for identity/clinical tiers (see consolidation plan soak table).
- Railway token gap:
LAST1_PLATFORM_RAILWAY_PROD_API_TOKENstored in BWSlast1-platform(2026-08-24 Tier 1 closeout). - Schedule mutex: Disable source schedules before enabling target (W7 order preserved).
Recommended soak by tier (Tier 2–6)¶
| Tier | Product | Recommended soak |
|---|---|---|
| 2 | last1-admin compute | 24–48h (internal-only) |
| 3 | RealOutcomes (+ IRS cron) | 48–72h (real customer data) |
| 4 | last1.id | 7 days minimum (foundational identity) |
| 5 | Onset clinic + Supabase | 7 days minimum (clinical / SOC 2) |
| 6 | last1.app + RadioCheck bundle | 7 days minimum (shared DB complexity) |
Muster¶
migrate-n8n-railway-to-r940 → done (R940 live; Railway service deleted 2026-08-24).