Deployment
Prerequisites
xbrain deploys as a Docker Compose stack on a Linux VM. GCP e2-medium is the recommended starting point for Phase 1 (~25€/mo). All services run as Docker containers — no Kubernetes required.
VM Sizing by Phase
Phase 1 runs on a 4 GB VM (e2-medium, ~25€/mo on GCP). Before starting Phase 2, upgrade to 8 GB (e2-standard-2, ~49€/mo). Phase 3+ may require 16 GB depending on load.
| Requirement | Details |
|---|---|
| Cloud VM | GCP e2-medium (4 GB, 2 vCPU) for Phase 1 |
| Operating System | Ubuntu 24.04 LTS |
| Docker | 24.0+ (install via official script) |
| Docker Compose | v2 (included with Docker or via apt install docker-compose-plugin) |
| Firewall | TCP 80 and 443 open (GCP firewall rules) |
| Domain (optional) | For HTTPS via Cloudflare DNS proxy |
| Google OAuth App | Client ID + Secret from Google Cloud Console (for SSO) |
Phase 1: Core Stack
Phase 1 deploys nginx, PostgreSQL, Qdrant, memory-api, LibreChat, Open WebUI, and the memory bridges. This is the minimal stack that satisfies the core memory invariant: every conversation is stored with the 7-field tagging contract enforced.
Step 1 — Create a GCP VM
bash# Create e2-medium VM (GCP Console or gcloud CLI)
gcloud compute instances create xbrain-vm \
--zone=europe-west1-b \
--machine-type=e2-medium \
--image-family=ubuntu-2404-lts \
--image-project=ubuntu-os-cloud \
--boot-disk-size=50GB \
--tags=http-server,https-server
# Open HTTP/HTTPS ports
gcloud compute firewall-rules create xbrain-http \
--allow tcp:80,tcp:443 \
--target-tags http-server,https-server
Step 2 — Install Docker
bashssh user@YOUR_VM_IP
# Install Docker (official script)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
# Verify
docker --version # Docker 24.x+
docker compose version # Docker Compose version v2.x
Step 3 — Clone and configure
bashgit clone https://github.com/mrboups/xbrain.git
cd xbrain/infrastructure
cp .env.example .env
nano .env # Fill in your values (see Configuration page)
Required .env values for Phase 1:
.env — Phase 1 requiredPOSTGRES_PASSWORD=<strong-random-password>
GOOGLE_CLIENT_ID=<from-google-cloud-console>
GOOGLE_CLIENT_SECRET=<from-google-cloud-console>
BRIDGE_SHARED_SECRET=$(openssl rand -hex 32)
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
MEILI_MASTER_KEY=$(openssl rand -hex 32)
PIPELINE_API_KEY=$(openssl rand -hex 32)
Step 4 — Start Phase 1
bashdocker compose up -d
# Monitor startup (takes ~60s for all healthchecks to pass)
docker compose ps
watch docker compose ps # Wait for all to show "healthy"
Step 5 — Configure nginx
bash# Edit nginx config with your VM IP or domain
nano infrastructure/nginx/conf.d/10-xbrain.conf
# Restart nginx to apply
docker compose restart nginx
Step 6 — Verify Phase 1
bashbash infrastructure/scripts/verify-phase1.sh
# Expected: PASS: 6 / 6
First Login
Access LibreChat at http://YOUR_VM_IP. Sign in with Google — the email
must match a configured team scope. Open WebUI is available at
http://YOUR_VM_IP/open-webui.
Phase 2: Intelligent Memory
Phase 2 adds agent-runtime (LangGraph), Langfuse observability, mem0, Redis, MinIO, and ClickHouse. This phase requires more RAM — upgrade before starting.
VM Upgrade Required
Before starting Phase 2, upgrade your VM from e2-medium (4 GB) to e2-standard-2 (8 GB). Adding Phase 2 services to a 4 GB VM will cause OOM kills.
bash — resize VM# GCP Console: stop VM → Edit → Change machine type → e2-standard-2 → Start
# Or via gcloud CLI:
gcloud compute instances stop xbrain-vm
gcloud compute instances set-machine-type xbrain-vm --machine-type=e2-standard-2
gcloud compute instances start xbrain-vm
Add to .env:
.env — Phase 2 additionsLANGFUSE_PUBLIC_KEY=<from-langfuse-setup>
LANGFUSE_SECRET_KEY=<from-langfuse-setup>
bash# Phase 2 services are already defined in docker-compose.yml
docker compose up -d
bash infrastructure/scripts/verify-phase2.sh
# Expected: PASS: 6 / 6
Langfuse UI is available at http://YOUR_VM_IP:3000 (or obs.yourdomain.com
if using a custom domain). On first boot, create an admin account and generate your
LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY.
Phase 3: Graph + Integrations
Phase 3 adds Neo4j (knowledge graph), mcp-gateway, mcp-scraper, mcp-drive-read, mcp-calendar, and drive-sync. MCP tools are registered with memory-api after startup.
Add to .env:
.env — Phase 3 additionsNEO4J_PASSWORD=<strong-password>
OAUTH_CREDENTIALS_ENCRYPTION_KEY=$(python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())")
# GOOGLE_CLIENT_SECRET must already be set from Phase 1
bashdocker compose up -d
# Register MCP tools with memory-api
bash infrastructure/scripts/register-mcp-tools.sh
# Verify
bash infrastructure/scripts/verify-phase3.sh
# Expected: PASS: 7 / 7
Google Drive OAuth Setup
For Drive sync, you must add your VM's callback URL to your Google OAuth app's
Authorized Redirect URIs:
https://api.yourdomain.com/v1/oauth/callback. Users then authorize
from their LibreChat profile page.
Phase 4: MCP Completion
Phase 4 adds mcp-deck (PPTX generation) and upgrades the LibreChat bridge for full multi-tool support. No new environment variables are required.
bash# Build mcp-deck (local build required)
docker compose build mcp-deck
# Restart stack
docker compose up -d
# Verify
bash infrastructure/scripts/verify-phase4.sh
# Expected: PASS: 8 / 8
Phase 5: Team Platform
Phase 5 adds graphiti-service (temporal fact extraction), Chrome extension web clipper,
and projects-dashboard. Auth was originally an OAuth App + long-lived
GITHUB_API_PAT — both retired in Phase 12 in favour of the
xbrain GitHub App (see Phase 12 below).
Add to .env:
.env — Phase 5 additionsGRAPHITI_SERVICE_URL=http://graphiti-service:8300
GITHUB_ORG=your-github-org # Display only post-Phase 12
bashdocker compose up -d
bash infrastructure/scripts/verify-phase5.sh
# Expected: PASS: 8 / 8
GITHUB_API_PAT removed in Phase 12
The legacy long-lived GITHUB_API_PAT env var has been removed. All org-membership
lookups now flow through the xbrain GitHub App's short-lived installation tokens
(ghs_, ~1h TTL). See the Phase 12 section
for the GitHub App registration runbook.
Phase 11: Brain Monitor + Superadmin Dashboard
Phase 11 introduces the universal Brain Monitor (single feed across the 7 brainable entity
types), the /account/admin/ superadmin dashboard, and a daily hard-purge cron
container (brain-janitor).
Migrations (apply automatically on memory-api boot):
0017_brain_monitor_base— addstruth_level,deleted_at,deleted_bytoconversations,messages,team_messages,tasks, andcontacts.0018_brain_events_view— creates thev_brain_eventsview (UNION-ALL across the 7 entity types).
Add to .env:
.env — Phase 11 additions# Superadmins for /v1/admin/brain/*.
# Comma-separated subs (e.g. "github:mrboups,google:1087654...").
# Empty value (or unset) disables ALL superadmin endpoints (lockdown kill-switch).
ADMIN_USER_SUBS=github:mrboups
# MinIO (used by /v1/admin/brain/storage — N/A on miss, never 500).
MINIO_URL=http://xbrain-minio:9000
MINIO_ACCESS_KEY=...
MINIO_SECRET_KEY=...
MINIO_BUCKET=xbrain-default
bashdocker compose up -d brain-janitor memory-api
bash infrastructure/scripts/verify-phase11.sh
# Expected: PASS: 5 / 5 (with 11 SKIPped optional fixtures)
brain-janitor health
The brain-janitor container runs at 03:00 UTC daily. Liveness sentinel:
/tmp/brain-janitor-alive must be touched within the last 25 hours.
Verification assertion 11 SKIPs on cold start (sentinel not yet created); FAILs after
the first cycle if the sentinel is stale.
Phase 12: GitHub App Migration
Phase 12 migrates auth from the legacy OAuth App + long-lived GITHUB_API_PAT
to a GitHub App. The new App supports multi-callback (web + Chrome extension), short-lived
installation tokens (ghs_ ~1h), user-token refresh
(ghu_/ghr_, ~6 month sliding window), and install/uninstall webhooks.
Step 1 — Derive the Chrome extension ID FIRST
The GitHub App's callback URL must include
https://<ext-id>.chromiumapp.org/. The
Chrome extension uses a manifest.json "key" to pin the
extension ID so it stays stable across dev machines. For Phase 12 the canonical ID is:
chrome-ext-idanigikcnmldoklcmogffmgcojdhhficb
See the Chrome Extension guide for the keypair-generation + manifest "key" derivation procedure.
Step 2 — Register the App on GitHub
At github.com/settings/apps → New GitHub App:
- GitHub App name:
xbrain(orxbrain-appif taken). - Homepage URL:
https://grooveos.app - Callback URLs (one per line):
https://grooveos.app/account/teams/ https://anigikcnmldoklcmogffmgcojdhhficb.chromiumapp.org/ - Webhook URL:
https://api.grooveos.app/v1/webhooks/github/installation - Webhook secret:
openssl rand -hex 32→ save forGITHUB_APP_WEBHOOK_SECRET. - Permissions: Organization → Members Read; Account → Email addresses Read; Account → Profile Read. No repo permissions.
- Subscribe to events: Installation, Installation repositories. Uncheck everything else.
- Optional features: check User-to-server token expiration — without this, tokens are unbounded and no refresh tokens are issued.
Step 3 — Capture secrets, encode the PEM
After saving the App, capture:
bash — encode PEM as single linebase64 -w 0 < xbrain.<date>.private-key.pem
# macOS: base64 -i xbrain.<date>.private-key.pem | tr -d '\n'
Add to .env (5 new vars in Phase 12; GITHUB_APP_SLUG is optional):
.env — Phase 12 additionsGITHUB_APP_ID=3743573
GITHUB_APP_SLUG=xbrain
GITHUB_APP_CLIENT_ID=Iv23liVnZvIN0Lo6isof
GITHUB_APP_CLIENT_SECRET=<from App settings>
GITHUB_APP_PRIVATE_KEY_B64=<single-line base64 of the PEM>
GITHUB_APP_WEBHOOK_SECRET=<from Step 2>
# REMOVE from .env (replaced by App JWT + installation tokens):
# GITHUB_API_PAT=
# GITHUB_ORG_PAT=
Step 4 — Restart and verify
bashdocker compose up -d memory-api
docker exec xbrain-memory-api printenv | grep ^GITHUB_APP_ # must show 6 vars
export MEMAPI_HOST=https://api.grooveos.app
export GITHUB_APP_CLIENT_ID=Iv23liVnZvIN0Lo6isof
export GITHUB_APP_WEBHOOK_SECRET=<value from .env>
export TEST_GITHUB_ORG=dejavudev # any org with the App installed
bash infrastructure/scripts/verify-phase12.sh
# Expected: PASS ≥ 13/13 (some assertions SKIP without fixtures)
Step 5 — Install the App on an org
Visit https://github.com/apps/<your-slug> → Configure
→ pick your org → Install on All repositories. The
POST /v1/webhooks/github/installation webhook fires within ~30 s and creates
the installations row. If the webhook is delayed, the next signin self-heals
via the hybrid installation lookup.
Phase 12 retires GITHUB_API_PAT entirely
After Phase 12 LIVE, verify-phase12.sh assertion 6 fails the deploy if any
import of GITHUB_API_PAT remains in apps/memory-api/. The legacy
OAuth App (Ov23liy7tZekl0uEztoj) should be revoked 24h+ after the new App
passes UAT — see the oauth-app-revocation.md runbook in
.planning/KB/.
HTTPS with Cloudflare
Cloudflare provides free HTTPS via their DNS proxy. Add your domain to Cloudflare, point an A record to your VM IP, and enable the proxy (orange cloud icon). Cloudflare terminates TLS and proxies HTTP to your VM on port 80.
Cloudflare Proxy Mode
With the orange cloud enabled, Cloudflare handles TLS — your nginx only needs to listen on port 80. Set SSL/TLS mode to "Flexible" in Cloudflare if your VM has no certificate, or "Full" if you add a self-signed cert on the VM.
Key nginx configuration for proxying to services:
nginx — 10-xbrain.conf (excerpt)server {
listen 80;
server_name x.yourdomain.com;
location / {
proxy_pass http://librechat:3080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
}
}
server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://memory-api:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Domains used in the reference deployment:
chat.grooveos.app— LibreChatadm.grooveos.app— Open WebUIapi.grooveos.app— memory-apiobs.grooveos.app— Langfuse
Backups
xbrain ships with backup and restore scripts for PostgreSQL, Qdrant, and Neo4j volumes. Run backups on a cron schedule or before major upgrades.
bash# Create a full backup (dumps PostgreSQL + archives named volumes)
bash infrastructure/scripts/backup.sh
# Output: backups/backup_20260501_120000.tar.gz
# Restore on a fresh VM (after cloning repo + setting .env)
bash infrastructure/scripts/restore.sh backups/backup_20260501_120000.tar.gz
Recommended cron schedule for automated backups:
crontab# Daily backup at 2:00 AM UTC
0 2 * * * cd /home/user/xbrain/infrastructure && bash scripts/backup.sh >> /var/log/xbrain-backup.log 2>&1
Monitoring VM Resources
With 25+ containers running, memory and disk usage require regular monitoring. These commands give you an instant view of the stack's health.
bash# Real-time container memory and CPU usage
docker stats --no-stream
# Check disk space (Docker layers + volumes are the main consumers)
df -h
# See how much Docker is using
docker system df
# If disk usage exceeds 90%, clean up unused images and stopped containers
docker system prune --volumes -f
Disk Usage Warning
The VM disk (50 GB recommended) can fill up quickly with Docker image layers, logs,
and Qdrant vector data. If disk usage exceeds 95%, services will fail to write data.
Run docker system prune or expand the GCP boot disk before adding new
services or phases.
Check for containers in a restart loop or unhealthy state:
bash# List all containers with health status
docker compose ps
# Tail logs for a specific service
docker compose logs -f memory-api
# Check recent events (OOM kills, restarts)
docker events --since 1h
Upgrading
To upgrade xbrain to a newer version, pull the latest code and rebuild changed images. Always run a backup before upgrading.
bash# 1. Take a backup first
bash infrastructure/scripts/backup.sh
# 2. Pull latest code
git pull origin main
# 3. Pull updated Docker images
docker compose pull
# 4. Rebuild locally-built services (memory-api, agent-runtime, bridges, MCP tools)
docker compose build
# 5. Restart the stack
docker compose up -d
# 6. Check for any failed healthchecks
docker compose ps
Troubleshooting
Services not starting
Check logs for the failing service:
bashdocker compose logs memory-api
docker compose logs librechat
docker compose logs postgres
LibreChat can't connect to memory-api
Ensure BRIDGE_SHARED_SECRET is identical in both the bridge environment and
the memory-api environment. Mismatched secrets cause 401 errors on every memory write.
OAuth login fails
Verify that GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are set
correctly and that the OAuth callback URI is registered in Google Cloud Console.
The expected callback URL is https://api.yourdomain.com/v1/oauth/callback.
Out of memory (OOM) kills
If you see containers restarting with exit code 137, the VM is running out of memory.
Check docker stats to identify the largest consumer and either upgrade
the VM or disable non-essential services.
Next Steps
- Configuration reference — Full list of environment variables with types, defaults, and generation commands.
- Architecture overview — All 25 containers and how data flows between them.
- MCP Tools guide — How to use the Drive, Calendar, Scraper, and Deck tools from LibreChat.
- Agents — Building LangGraph agents that read and write to the shared memory layer.