Skip to content

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:

ssh ryan@192.168.1.94 'curl -s http://127.0.0.1:5678/healthz'
# {"status":"ok"}

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:

  1. Bearer Auth account (social scheduler / Instagram GraphQL)
  2. Header Auth accounts (×2)
  3. Google Sheets OAuth
  4. 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:

  1. Railway schedules disabled — Workflow 1 + 2 deactivated on n8n.theonset.app.
  2. R940 schedules enabled — Workflow 1 + 2 active on VM 112.
  3. Ingress — cloudflared on lastvet-edge (192.168.1.89): n8n.last1.enterprises → http://192.168.1.94:5678.
  4. DNS — Cloudflare CNAME n8n.last1.enterprises → {tunnel-id}.cfargotunnel.com (proxied). Removed mistaken n8n.last1.enterprises.last.vet record.
  5. n8n env — /etc/n8n/env updated: N8N_HOST=n8n.last1.enterprises, N8N_PROTOCOL=https, secure cookies on, webhook/editor URLs on https://n8n.last1.enterprises/.
  6. 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 to n8n.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)

  1. Workflow audit beats manual count: Ryan said 3 workflows; API found 4 (inactive duplicate Content Generator). Always export via API before cutover.
  2. Cloudflare + n8n API: Public API requires browser User-Agent or Cloudflare returns 1010.
  3. Credential re-entry: Fresh N8N_R940_PROD_ENCRYPTION_KEY means 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).
  4. Ingress pattern: cloudflared on lastvet-edge routes n8n.last1.enterprises → LAN 192.168.1.94:5678; DNS CNAME to {tunnel-id}.cfargotunnel.com.
  5. BWS naming: N8N_R940_PROD_* under last1-platform project works; store N8N_R940_PROD_API_KEY after UI rotation.
  6. 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).
  7. Railway token gap: LAST1_PLATFORM_RAILWAY_PROD_API_TOKEN stored in BWS last1-platform (2026-08-24 Tier 1 closeout).
  8. Schedule mutex: Disable source schedules before enabling target (W7 order preserved).
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).