Skip to content

Baseworks n8n CrewAI Deployment Guide

Created 2026-02-18
Updated 2026-03-02
Status archived
Tags agent-systemdeploymentguideinfrastructurearchived

ARCHIVED — 2026-03-02 CrewAI has been decommissioned. Intelligence tasks now run through Claude Code on the repurposed OpenClaw VPS, using Patrick’s Claude Max account and Asia’s Claude Pro account. No OpenRouter or API keys in use. n8n remains active in a reduced role: Slack routing, git sync, scheduled triggers — no AI calls. See: Claude-Code-VPS-Setup

Deployment Guide — Baseworks Agent System

Section titled “Deployment Guide — Baseworks Agent System”

Step-by-step guide to deploy the n8n + CrewAI agent system on Hetzner via xCloud.

Estimated total time: 2-3 hours for Phases 1-5, plus Phase 6 (Remote MCP for mobile) as a follow-up


Phase 1: VPS & n8n Setup — COMPLETED 2026-02-19

Section titled “Phase 1: VPS & n8n Setup — COMPLETED 2026-02-19”

1.1 Provision Hetzner Server via xCloud — DONE

Section titled “1.1 Provision Hetzner Server via xCloud — DONE”
  1. Log in to xCloud dashboard
  2. Add Server → Hetzner → Select CX33 (4 vCPU, 8GB RAM, 80GB SSD — €4.99/mo)
  3. Server type: Docker+NGINX (not LEMP — we need Docker for the full stack)
  4. App Type: None (leave blank — we deploy n8n ourselves via Docker)
  5. Database: MySQL (default — irrelevant, n8n uses PostgreSQL in Docker)
  6. Region: Falkenstein, DE (Central) (close to existing Baseworks sites in Germany)
  7. Ubuntu: 24.04 LTS
  8. Plan: Lifetime (Seven Server LTD)
  9. Hetzner Auto Backups: Enabled (+€1/mo)
  10. Server name: baseworks-agents
  11. Tags: agents, n8n, docker

Actual server details:

  • IP: 167.235.236.99
  • SSH: ssh patrick@167.235.236.99 (passwordless sudo configured)
  • Docker: 29.2.1 + Compose v5.0.2
  • NGINX: 1.28.1
  • Git: 2.43.0
  • Ubuntu: 24.04 LTS (kernel 6.8.0-90-generic)

1.2 Deploy n8n via Docker Compose — DONE

Section titled “1.2 Deploy n8n via Docker Compose — DONE”

We did NOT use xCloud’s one-click n8n app. Instead, n8n runs in Docker alongside PostgreSQL and CrewAI, managed by a single docker-compose.yml.

Docker Compose file: 03-resources/agent-system/docker/docker-compose.yml Environment file: /opt/baseworks-vault/03-resources/agent-system/docker/.env (on VPS, not in git)

Services running:

  • baseworks-postgres — PostgreSQL 16 (Alpine) on port 5432
  • baseworks-n8n — n8n (latest) on 127.0.0.1:5678
Terminal window
# Start/restart the stack
ssh patrick@167.235.236.99
cd /opt/baseworks-vault/03-resources/agent-system/docker
sudo docker compose up -d
# Check status
sudo docker ps

SSH user patrick created via xCloud Sudo Users panel with “Patricks Desktop Mac” SSH key. Passwordless sudo configured via xCloud Commands: echo "patrick ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/patrick

Terminal window
# Connect from Patrick's Mac
ssh patrick@167.235.236.99

Note: Ksenia’s SSH key should be added via xCloud → Sudo Users when she needs access.

Vault cloned to /opt/baseworks-vault using GitHub PAT named “Baseworks-agents-VPS”.

Terminal window
# Vault location on VPS
/opt/baseworks-vault
# Git config
user.name: "Baseworks Agent"
user.email: "agent@baseworks.com"

GitHub PAT rotation reminder: Token was created 2026-02-19 with 90-day expiry. Rotate by ~2026-05-19.

Originally a root cron job pulled every 5 minutes. The cron job was removed on 2026-02-20 because it created root-owned files in .git/ that broke n8n container access (n8n runs as uid 1000).

The n8n “Vault Git Sync” workflow (ID: Oh75ZJIJiIZreS06) is now the sole sync mechanism, pulling every 5 minutes. The .git directory ownership was fixed: chown -R 1000:1000 /opt/baseworks-vault/.git and a persistent gitconfig was added via GIT_CONFIG_GLOBAL=/home/node/.n8n/.gitconfig.

Domain: n8n.baseworks.com DNS: Cloudflare A record → 167.235.236.99 (proxy ON, orange cloud) SSL: Cloudflare Origin Certificate (wildcard *.baseworks.com, expires 2041-02-15)

Certificate files on VPS:

  • /etc/ssl/cloudflare/baseworks.com.pem
  • /etc/ssl/cloudflare/baseworks.com.key

NGINX config: /etc/nginx/sites-available/n8n (port 80 redirects to 443, proxies to 127.0.0.1:5678)

Access n8n at: https://n8n.baseworks.com

  • Owner account: Baseworks Team (agents@baseworks.com)
  • Free license key activated (advanced debugging, execution search, folders)
  • Individual user accounts for Patrick and Ksenia to be added

1.8 Additional Directories Created — DONE

Section titled “1.8 Additional Directories Created — DONE”
/opt/baseworks-crewai — CrewAI deployment directory
/var/log/baseworks — Application logs

Phase 2: CrewAI Setup — COMPLETED 2026-02-19

Section titled “Phase 2: CrewAI Setup — COMPLETED 2026-02-19”

OpenRouter API key added to .env on VPS.

Fixed FileReadTool import (moved from crewai.tools to crewai_tools). Added crewai-tools>=0.14.0 to requirements.txt.

All three containers running and healthy:

baseworks-crewai healthy 127.0.0.1:8000
baseworks-n8n healthy 127.0.0.1:5678 → https://n8n.baseworks.com
baseworks-postgres healthy 5432
Terminal window
curl http://127.0.0.1:8000/health
# {"status":"healthy","running_tasks":0}

Phase 2.5: Operations Dashboard — NOT STARTED

Section titled “Phase 2.5: Operations Dashboard — NOT STARTED”

A custom web dashboard at crews.baseworks.com providing a single pane of glass for the entire agent system. Built directly into the existing CrewAI FastAPI app — no external tools.

SectionWhat it shows
Agent RosterAll agents, roles, goals, tools (read from YAML configs)
Crew MapVisual flow of which agents work together in each crew
Execution HistoryEvery crew run — input, output, duration, token cost
Live StatusCurrently running crews, queue depth
Server HealthCPU, RAM, disk usage, container status
Vault SyncLast pull time, sync status, recent file changes
OpenRouter SpendToken usage, cost per crew, daily/weekly totals
Workflow StatusWhich n8n workflows are active, recent failures
Backup StatusLast Hetzner snapshot, Backblaze backup status
Secret RotationGitHub PAT expiry date, API key ages, rotation reminders
System LogUnified timeline of events across all services
  • Lightweight HTML templates served by FastAPI (no heavy frontend framework)
  • Reads agent/task configs from YAML files in the vault
  • Queries Docker API for container health and resource usage
  • Queries n8n API for workflow status
  • Tracks execution history in a local SQLite database
  • Basic auth or same credentials as n8n
  1. Add dashboard routes to CrewAI FastAPI app
  2. Create HTML templates for each dashboard section
  3. Add Cloudflare DNS: crews.baseworks.com167.235.236.99
  4. Add NGINX config (same pattern as n8n, uses existing wildcard cert)
  5. Expose port 8000 via NGINX for the dashboard (CrewAI API + dashboard on same port)

Phase 3: Slack Setup — COMPLETED 2026-02-19

Section titled “Phase 3: Slack Setup — COMPLETED 2026-02-19”

Using existing baseworks.slack.com workspace.

5 private channels created:

ChannelIDPurpose
#wordpress-updatesC0AFWBZDDR9Site monitoring alerts & actions
#forum-responsesC0AG6BZEA2YDraft responses for approval
#content-strategyC0AG0MUFE1LContent ideas & drafts
#agent-alertsC0AGFM6AZ09System status, errors, kill switch
#vault-inboxC0AG0MWBP5LMobile vault editor (Slack → Obsidian)
  • App name: “Baseworks Agent”
  • App ID: A0AG6CC8CG4
  • Bot invited to all 5 private channels
  • Scopes: chat:write, chat:write.public, channels:history, channels:read, channels:join, groups:history, groups:read, reactions:write, users:read

Slack API credential (“Slack account”) created and tested in n8n.


Phase 4: Import n8n Workflows — COMPLETED 2026-02-19

Section titled “Phase 4: Import n8n Workflows — COMPLETED 2026-02-19”

Important lesson: n8n’s UI “Import from File” rejects hand-crafted JSON with a “could not find property option” error. The CLI import (n8n import:workflow) accepts it but the UI can’t render the nodes. The only reliable method is the n8n Public API (POST /api/v1/workflows).

Correct Slack node format (discovered through testing):

{
"select": "channel",
"channelId": {"__rl": true, "value": "CHANNEL_ID", "mode": "list", "cachedResultName": "channel-name"},
"text": "message text",
"otherOptions": {}
}

All 7 workflows created via the API and rendering correctly:

Workflown8n IDStatus
Vault Git SyncOh75ZJIJiIZreS06Imported, inactive
Kill Switch — Emergency Stopv4T9Xu39sxR5CJnBImported, inactive
Daily Vault Summary3QEX4wna4XMr348KImported, inactive
Vault Capture via SlackA0hTmPJN38HRe3ChImported, inactive
WordPress Monitoring & Updates1uUisQfjZ0TUqiM4Imported, inactive
Forum Response PipelineshLtsDHV6zIqySZ0Imported, inactive
Content Creation Pipelineq1eV3z1aQs9VWh67Imported, inactive

4.2 Configure Each Workflow — PARTIALLY DONE

Section titled “4.2 Configure Each Workflow — PARTIALLY DONE”

Slack credential: Auto-connected to all 14 Slack nodes (only one credential exists). Channel IDs: All Slack nodes configured with correct channel IDs via API script.

Still needed:

  • WordPress Monitoring: HTTP Basic Auth credential (WP username + application password) + WP_URL env var
  • Forum Response: FORUM_API_URL environment variable
  • Slack Event Subscriptions: Need to configure in Slack app settings to forward messages to webhook URLs

4.3 Activate Workflows — PARTIALLY COMPLETE (2026-02-20)

Section titled “4.3 Activate Workflows — PARTIALLY COMPLETE (2026-02-20)”

6 of 8 workflows are active. Activation order used:

  1. ✅ Vault Git Sync — ACTIVE
  2. ✅ Kill Switch — ACTIVE (tested end-to-end)
  3. ✅ Daily Vault Summary — ACTIVE
  4. ❌ WordPress Monitoring — INACTIVE (needs WP_URL + HTTP Basic Auth)
  5. ✅ Vault Capture via Slack — ACTIVE
  6. ❌ Forum Response Pipeline — INACTIVE (needs FORUM_API_URL)
  7. ✅ Content Creation Pipeline — ACTIVE
  8. ✅ Slack Event Router — ACTIVE (new workflow, see Phase 4.6)

Still needed to activate remaining workflows:

  • WordPress Monitoring: HTTP Basic Auth credential (WP username + application password), WP_URL env var, clone p-oancia/baseworks-changelog repo to VPS
  • Forum Response: FORUM_API_URL environment variable
  • Label: “Baseworks Deploy”
  • Expires: 2026-03-22 (30 days)
  • All scopes enabled
  • Used for workflow creation/management via API

4.5 Configure n8n Environment Variables — NOT STARTED

Section titled “4.5 Configure n8n Environment Variables — NOT STARTED”

In n8n → Settings → Environment Variables:

WP_URL=https://baseworks.com
FORUM_API_URL=https://your-forum-api.com

Phase 4.6: Slack Event Subscriptions — COMPLETED 2026-02-20

Section titled “Phase 4.6: Slack Event Subscriptions — COMPLETED 2026-02-20”

Slack Event Subscriptions configured to forward channel messages to n8n.

Since Slack only allows one Request URL per app, we created a dedicated “Slack Event Router” workflow (ID: fcsJFL21APR2uyV9) that:

  1. Receives all Slack events at a single URL
  2. Handles Slack’s URL verification challenge
  3. Routes events to the correct workflow based on channel ID

Slack App Settings (https://api.slack.com/apps/A0AG6CC8CG4):

  • Event Subscriptions: Enabled
  • Request URL: https://n8n.baseworks.com/webhook/slack-events
  • Subscribed bot event: message.groups (private channels)

Routing Table:

Source ChannelChannel IDRoutes To
#agent-alertsC0AGFM6AZ09Kill Switch (/webhook/kill-switch)
#vault-inboxC0AG0MWBP5LVault Capture (/webhook/vault-capture)

Architecture: Event Router → IF node per channel → HTTP Request to internal webhook → target workflow handles the event. Both Kill Switch and Vault Capture workflows also handle the URL verification challenge independently (belt and suspenders).


Phase 5: Testing — PARTIALLY COMPLETE (2026-02-20)

Section titled “Phase 5: Testing — PARTIALLY COMPLETE (2026-02-20)”

Tested end-to-end on 2026-02-20:

  1. Typed STOP in #agent-alerts → Received “EMERGENCY STOP EXECUTED” confirmation
  2. Typed RESUME in #agent-alerts → Received “RESUMED” confirmation
  3. Full event flow verified: Slack → Event Router → Kill Switch → Slack response
  1. Add a test file to vault on your Mac
  2. Commit and push
  3. Wait 5 minutes
  4. SSH to VPS: ls /opt/baseworks-vault/00-inbox/
  5. Your file should be there
  1. Manually execute the “Daily Vault Summary” workflow in n8n
  2. Check #agent-alerts for the summary message
  1. In Slack #vault-inbox, type: “Test note — this is a capture test”
  2. Should get a filing suggestion with Confirm/Cancel buttons
  3. Confirm and check vault
  1. Manually trigger with a test post in n8n (or via webhook)
  2. Check #forum-responses for the draft
  3. Test Approve/Edit/Reject buttons
  1. Manually execute the “Content Creation Pipeline” in n8n
  2. Check #content-strategy for proposals
  3. Test approval buttons
  1. Manually execute the “WordPress Monitoring” workflow
  2. Check #wordpress-updates for status

Phase 6: Remote MCP Server (Mobile Access) — NOT STARTED

Section titled “Phase 6: Remote MCP Server (Mobile Access) — NOT STARTED”

Enables Claude on iOS/Android to talk directly to the agent infrastructure — no Slack middleman needed for vault capture and crew execution from phone conversations.

The remote MCP server is a lightweight authenticated API that wraps the same endpoints n8n already calls.

Terminal window
ssh patrick@167.235.236.99
cd /opt/baseworks-mcp-remote
sudo docker compose up -d --build
# Verify
curl http://127.0.0.1:8001/health

Add Cloudflare DNS A record: mcp.baseworks.com167.235.236.99 (proxy ON) NGINX config same pattern as n8n — uses the existing wildcard origin cert.

The remote MCP server exposes these tools to Claude:

ToolDescription
vault-captureSave a note/insight to the vault with auto-filing
vault-searchSearch the vault for existing knowledge
trigger-crewTrigger a CrewAI crew (forum-response, content-strategy, research)
crew-statusCheck running crew tasks
kill-switchEmergency stop all crews
  1. Go to https://claude.ai → Settings → Integrations (or MCP Servers)
  2. Add Remote MCP Server
  3. URL: https://mcp.baseworks.com
  4. Authenticate with API key (generated during deploy)
  5. Do this for both Patrick’s and Ksenia’s Anthropic accounts
  1. Open Claude iOS app on your phone
  2. Start a new conversation
  3. The MCP tools should be available (vault-capture, trigger-crew, etc.)
  4. Test: Ask Claude to save a note — confirm it appears in the vault

6.6 Interim Workflow (Before Phase 6 is Complete)

Section titled “6.6 Interim Workflow (Before Phase 6 is Complete)”

Until the remote MCP server is deployed, use this workflow from mobile:

  1. Have your conversation with Claude on your phone
  2. Copy key insights → paste into Slack #vault-inbox
  3. n8n + CrewAI auto-files to vault
  4. For deeper follow-up, continue the conversation on Claude Desktop (conversations sync across devices)

Edit vault files locally, git push. The VPS pulls automatically. Agent behavior updates on next execution.

Key files that affect agent behavior:

  • 03-resources/agent-system/crewai/config/agents.yaml — Agent personalities
  • 03-resources/agent-system/crewai/config/tasks.yaml — Task instructions
  • 02-areas/communications/communications-guide.md — Response policies
  • 02-areas/community-forums-groups/index.md — Classification rules

If you change Python code (not just YAML/knowledge):

Terminal window
ssh patrick@167.235.236.99
cd /opt/baseworks-vault/03-resources/agent-system/docker
sudo docker compose up -d --build crewai
  • n8n execution history: https://n8n.baseworks.com → Executions
  • CrewAI health: curl http://127.0.0.1:8000/health
  • CrewAI running tasks: curl http://127.0.0.1:8000/status
  • Docker logs: sudo docker compose logs crewai -f
  • OpenRouter spend: https://openrouter.ai/usage
  • GitHub PAT (expires ~2026-05-19): Re-create at github.com/settings/tokens, update git remote URL on VPS
  • OpenRouter key: Update .envsudo docker compose restart crewai
  • Slack bot token: Update in n8n Credentials
  • n8n encryption key: In .env — do NOT change after initial setup (breaks encrypted credentials)
LayerWhatWhenCovers
Hetzner snapshotsFull server imageAutomated daily (+€1/mo)Catastrophic failure, OS corruption
Backblaze B2App data (DB dumps, configs)Daily + on-demand via n8n (to be configured)Accidental deletion, rollback
GitHubVault + codeEvery commitKnowledge base, deployment scripts

IssueCheck
n8n not accessiblesudo docker ps, NGINX config, Cloudflare DNS
CrewAI not respondingsudo docker compose logs crewai, check .env
Vault not syncingCheck n8n Git Sync workflow executions, git remote URL, PAT validity
Slack not receivingBot token scopes, channel IDs in workflows, Event Subscriptions config
High LLM costsOpenRouter usage page, MAX_TOKENS_PER_EXECUTION
Crew execution timeoutIncrease timeout in FastAPI (default 300s)
SSL/521 errorsCheck NGINX SSL cert paths, Cloudflare SSL mode (must be Full)
“Unrecognized node type: executeCommand”n8n v2 blocks this node by default. Set NODES_EXCLUDE=[n8n-nodes-base.localFileTrigger] in docker-compose (keeps localFileTrigger blocked, re-enables executeCommand)
CrewAI “connection refused” from n8nUse http://crewai:8000 (Docker service name), NOT http://127.0.0.1:8000. Localhost inside a container refers to that container only
Switch node routes everything to output 0Known bug in n8n 2.8.3 Switch node v3. Use IF nodes instead
Git “dubious ownership” in n8n containerRun chown -R 1000:1000 /opt/baseworks-vault/.git and ensure GIT_CONFIG_GLOBAL=/home/node/.n8n/.gitconfig is set in docker-compose
X-Forwarded-For trust proxy warningSet N8N_PROXY_HOPS=1 in docker-compose environment

n8n v2 Compatibility Notes (discovered 2026-02-20)

Section titled “n8n v2 Compatibility Notes (discovered 2026-02-20)”

These issues were found running n8n 2.8.3 and may apply to other v2 releases:

  1. executeCommand node blocked by default — n8n v2 blocks executeCommand and localFileTrigger for security. To re-enable executeCommand, set NODES_EXCLUDE=[n8n-nodes-base.localFileTrigger] (this keeps the file trigger blocked but allows command execution).

  2. Switch node v3 broken — Always routes items to output index 0 regardless of which condition matches. Workaround: use sequential IF nodes (n8n-nodes-base.if typeVersion 2) instead.

  3. Code node multi-output not supportedrunOnceForAllItems mode doesn’t support returning [[items], [items]] for multi-output routing. Return single-output items and use IF/Switch nodes for routing instead.

  4. Docker inter-container networking — Containers access each other via Docker Compose service names (e.g., crewai:8000), never 127.0.0.1.