Setup Guide -- cortex
July 30, 2026 · View on GitHub
Step-by-step instructions to get cortex running locally, in Docker, or as a Claude Code plugin.
Prerequisites
| Dependency | Version | Purpose |
|---|---|---|
| Rust | 1.86+ | Compiler toolchain |
| cargo | (bundled) | Build system and package manager |
| Docker | 24+ | Container deployment |
| Docker Compose | v2+ | Orchestration |
| just | latest | Task runner |
| openssl | any | Token generation |
| curl | any | Health checks |
| jq | any | JSON parsing (optional, for readable output) |
1. Clone the repository
git clone https://github.com/dinglebear-ai/cortex.git
cd cortex
2. Install Rust toolchain
If Rust is not installed:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default stable
3. Build
just build # Debug build
just release # Release build (optimized)
Or directly:
cargo build --release
4. Configure environment
cp .env.example .env
chmod 600 .env
Edit .env and set values as needed:
# Syslog listener
CORTEX_RECEIVER_HOST=0.0.0.0
CORTEX_RECEIVER_PORT=1514
# MCP server (default bind is loopback; 0.0.0.0 requires CORTEX_TOKEN)
CORTEX_HOST=127.0.0.1
CORTEX_PORT=3100
# Required for non-loopback binds: bearer auth on /mcp and OTLP endpoints
# openssl rand -hex 32
CORTEX_TOKEN=
# Storage
CORTEX_DB_PATH=/data/cortex.db
CORTEX_POOL_SIZE=8
CORTEX_SQLITE_PAGE_CACHE_MB=128
CORTEX_SQLITE_MMAP_MB=256
CORTEX_HEAVY_READ_CONCURRENCY=1
CORTEX_WAL_CHECKPOINT_MB=256
CORTEX_RETENTION_DAYS=90
# Log verbosity
RUST_LOG=info
See CONFIG for all environment variables.
5. Start locally
just dev
Or directly:
cargo run
The server reads config.toml in the working directory. Syslog listens on 0.0.0.0:1514 (UDP+TCP) and MCP on 127.0.0.1:3100 (HTTP) by default.
6. Start via Docker
just up
Or manually:
docker compose up -d
Docker uses defaults and env vars exclusively -- config.toml is not copied into the image.
7. Verify
just health
Or:
curl http://localhost:3100/health
Expected response:
{"status": "ok"}
Send a test syslog message and confirm it arrives:
logger -n localhost -P 1514 --tcp "test from $(hostname)"
curl -s -X POST http://localhost:3100/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"cortex","arguments":{"action":"tail","n":5}}}' | jq .
Optionally confirm the MCP Apps query widget resource is served (host-agnostic — no UI client required):
curl -s -X POST http://localhost:3100/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"resources/read","params":{"uri":"ui://cortex/query-widget"}}' | jq -r '.result.contents[0].mimeType'
# Expected: text/html;profile=mcp-app
See the MCP Apps query widget section for what the widget does and how non-UI hosts are unaffected.
8. Install as Claude Code plugin
/plugin marketplace add jmagar/claude-homelab
/plugin install cortex @jmagar-claude-homelab
Configure the plugin with your MCP URL and optional API token when prompted.
9. Configure syslog sources
Per-host forwarder configuration (rsyslog, syslog-ng, WSL2, UniFi, routers/appliances, port-514 redirect, firewall rules) lives in the README "Syslog Forwarder Setup" section:
- Linux hosts: rsyslog
/etc/rsyslog.d/99-remote.conf - WSL hosts: rsyslog with Tailscale IP
- UniFi: Settings > System > Advanced > Remote Syslog
- Routers/appliances: device syslog settings (Diagnostics > Syslog on ATT BGW-320)
- Docker hosts: host-local cortex agent streams container logs from each host's local Docker socket; the legacy pull mode below is optional compatibility coverage
Optional Docker host log ingest
The recommended deployment uses the host-local cortex agent, which reads Docker logs from unix:///var/run/docker.sock on each host and forwards them to cortex without changing Docker's daemon-level logging driver.
The CORTEX_DOCKER_* settings are a legacy central pull compatibility mode for explicit remote Docker Engine HTTP endpoints. If your hosts still expose a Docker-compatible endpoint, cortex can pull container logs from those hosts directly.
If the compatibility endpoint is docker-socket-proxy, expose only the read endpoints cortex needs:
CONTAINERS=1
EVENTS=1
PING=1
VERSION=1
POST=0
Set CORTEX_DOCKER_HOSTS to a comma-separated list of hostnames in .env:
CORTEX_DOCKER_INGEST_ENABLED=true
CORTEX_DOCKER_HOSTS=squirts,tootie,dookie
Each hostname resolves to http://<host>:2375. Use only on trusted private networks (e.g. tailscale).
The ingest loop follows existing containers, listens for container start events, records checkpoints in SQLite, and reconnects with backoff if a host is unavailable. Remote containers still start normally if cortex is down because this path does not use Docker's daemon-level syslog logging driver.
Plain http:// remote Docker endpoints require allow_insecure_http = true. Use that only on trusted private networks, firewall the endpoint so only cortex can connect, or put it behind authenticated TLS. CONTAINERS=1 exposes Docker's broader read-only container API to anything that can reach a docker-socket-proxy, not just the log endpoints cortex calls.
For Docker ingest integration testing, keep the default smoke test focused on UDP/TCP syslog, REST/CLI parity, and file-tail ingest. Host-local agent Docker streaming is covered by agent deployment tests. For the legacy central pull path, start cortex with CORTEX_DOCKER_INGEST_ENABLED=true against a disposable Docker-compatible HTTP fixture, emit a unique marker from a short-lived container, then verify it with search or tail. Container stdout/stderr rows should report source_ip as docker://<host>/<container>/<stream>. Container lifecycle events such as create, start, restart, die, stop, destroy, rename, and oom should report source_ip as docker-event://<host>/<container>/<action>.
Troubleshooting
"Connection refused" on health check
- Confirm the server is running:
docker compose psorps aux | grep cortex - Verify
CORTEX_PORTmatches the port you are curling - If running in Docker, remember port 3100 is published on
127.0.0.1only by default — setCORTEX_MCP_BIND=0.0.0.0(plusCORTEX_TOKEN) to reach it from other hosts
"401 Unauthorized" on tool calls
- Verify
CORTEX_TOKENin.envmatches the token configured in your MCP client - If behind a reverse proxy (SWAG), fix the token mismatch — keep
CORTEX_TOKENset and pass it through the proxy.
WARNING — do not "fix" a 401 by disabling auth. Setting
CORTEX_NO_AUTH=true+CORTEX_TRUSTED_GATEWAY_NO_AUTH=true(TrustedGatewayUnscoped) disables both authentication and the read/admin scope gates — including the write actionsack_error,unack_error, andnotifications_test. Use it only when an upstream gateway enforces auth before traffic reaches cortex and port 3100 is not published beyond loopback (CORTEX_MCP_BIND=127.0.0.1, the default). Never combine it with host-published ports. See docs/SECURITY.md.
No syslog messages arriving
- Confirm the syslog port is reachable:
nc -zvu <host> 1514 - Check iptables rules if redirecting 514 to 1514
- Verify rsyslog config on the sending host:
systemctl status rsyslog - Check Docker port mapping:
docker port cortex
Database errors at startup
- Ensure the data directory exists and is writable by UID 1000
- Check volume mounts:
docker inspect cortex | jq '.[0].Mounts' - Verify
CORTEX_DB_PATHpoints to a writable location
Plugin not discovered by Claude Code
- Run
/plugin listand confirm cortex appears - Check
~/.claude/plugins/cache/for the plugin directory - Re-run
/plugin marketplace add jmagar/claude-homelabto refresh
OAuth Authentication
cortex supports Google OAuth 2.0 in addition to the static bearer token. See docs/OAUTH.md for the full setup guide, including:
- Google Console configuration (redirect URI, credentials)
- Required env vars (
CORTEX_AUTH_MODE,CORTEX_PUBLIC_URL, Google client ID/secret) config.tomlfields foradmin_email, TTLs, and signing key path- Operator FAQ (revoking users, rotating the JWT key)