Docker Compose Deployment
Audience: developer or operator deploying Cortex on a self-hosted server.
Prerequisites
Section titled “Prerequisites”- Docker >= 24
- Docker Compose v2
- ~4 GB RAM for the full stack
- ~10 GB disk for initial deployment (grows with data)
Quick start
Section titled “Quick start”git clone https://github.com/arka-labs/arka-cortex.gitcd arka-cortexcp .env.example .env# Edit .env — set all required secrets (see below)docker compose -f docker-compose.example.yml up -dCheck health:
curl http://localhost:9096/health# {"status":"ok"}Services
Section titled “Services”The docker-compose.example.yml defines 9 services:
| Service | Image | Port | Purpose |
|---|---|---|---|
surrealdb | surrealdb/surrealdb:v2.1.7 | 8000 | Graph + relational database |
postgres | postgres:16-alpine | 5432 | Audit trail, documents, auth |
meilisearch | getmeili/meilisearch:v1.12 | 7700 | Full-text search |
minio | minio/minio:latest | 9000 (API), 9001 (console) | S3-compatible object storage |
minio-init | minio/mc:latest | — | Init container: creates the bucket |
ollama | ollama/ollama:latest | 11434 | Local embeddings + LLM |
cortex_api | custom build | 9096 | REST API |
cortex_mcp | custom build | 3001 | MCP server |
cortex_ui | custom build | 8080 | Web UI |
All ports are bound to 127.0.0.1 by default (except the UI). This means they are only accessible from localhost. Use a reverse proxy for external access.
Required secrets
Section titled “Required secrets”Edit your .env file with these required values:
# SurrealDBSURREAL_USER=rootSURREAL_PASS=<strong-password>
# PostgreSQLPG_PASSWORD=<strong-password>
# MeiliSearchMEILI_MASTER_KEY=<strong-key-at-least-16-chars>
# MinIOS3_ACCESS_KEY=<access-key>S3_SECRET_KEY=<secret-key>
# CortexCORTEX_API_KEYS=<api-key-1>,<api-key-2>ENCRYPTION_KEY=<32-bytes-base64>Generate an encryption key:
openssl rand -base64 32See the Environment Variables page for the full list.
Startup order
Section titled “Startup order”Docker Compose uses healthchecks to enforce the startup order:
surrealdb,postgres,meilisearch,minio,ollamastart in parallel.minio-initwaits forminioto be healthy, then creates the bucket and exits.cortex_apiwaits for all five backends to be healthy.cortex_mcpwaits forcortex_apiandollamato be healthy.cortex_uiwaits forcortex_apito start.
Reverse proxy (TLS)
Section titled “Reverse proxy (TLS)”For production, place a reverse proxy in front of the stack for TLS termination. Example with Caddy:
# Caddyfilecortex.example.com { handle /api/* { reverse_proxy localhost:9096 } handle /health { reverse_proxy localhost:9096 } handle /mcp/* { reverse_proxy localhost:3001 } handle { reverse_proxy localhost:8080 }}With nginx:
server { listen 443 ssl http2; server_name cortex.example.com;
ssl_certificate /etc/letsencrypt/live/cortex.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/cortex.example.com/privkey.pem;
location /api/ { proxy_pass http://127.0.0.1:9096; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }
location /health { proxy_pass http://127.0.0.1:9096; }
location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; }}For Let’s Encrypt with Caddy, TLS is automatic. For nginx, use certbot:
certbot --nginx -d cortex.example.comBackups
Section titled “Backups”SurrealDB
Section titled “SurrealDB”SurrealDB stores data in a Docker volume (surreal_data). Export via the HTTP API:
curl -s -u "${SURREAL_USER}:${SURREAL_PASS}" \ "http://localhost:8000/export" \ -H "NS: ${SURREAL_NS:-cortex}" \ -H "DB: ${SURREAL_DB:-cortex}" \ -o "backup-surreal-$(date +%Y%m%d-%H%M).surql"Note: the SurrealDB CLI surreal export does not support file: storage mode. Use the HTTP /export endpoint instead.
PostgreSQL
Section titled “PostgreSQL”docker compose -f docker-compose.example.yml exec postgres \ pg_dump -U "${PG_USER:-cortex}" "${PG_DATABASE:-cortex}" \ > "backup-pg-$(date +%Y%m%d-%H%M).sql"Use the MinIO client to mirror the bucket:
docker compose -f docker-compose.example.yml exec minio \ mc mirror /data/cortex /backup/cortex-$(date +%Y%m%d)Or use mc from the host:
mc alias set cortex http://localhost:9000 ${S3_ACCESS_KEY} ${S3_SECRET_KEY}mc mirror cortex/${S3_BUCKET:-cortex} ./backup-minio-$(date +%Y%m%d)Automated backups
Section titled “Automated backups”A cron job running twice daily (03:00 and 13:00 UTC) is recommended:
0 3,13 * * * /opt/cortex/scripts/backup.sh >> /var/log/cortex-backup.log 2>&1Health monitoring
Section titled “Health monitoring”API health endpoint
Section titled “API health endpoint”curl -sf http://localhost:9096/health# {"status":"ok"}MCP health endpoint
Section titled “MCP health endpoint”curl -sf http://localhost:3001/health# {"status":"ok"}Admin status (requires API key)
Section titled “Admin status (requires API key)”curl -H "Authorization: Bearer ${CORTEX_API_KEY}" \ http://localhost:9096/api/admin/statusReturns version, node/edge/document counts, and service health checks.
Docker healthchecks
Section titled “Docker healthchecks”All services have built-in Docker healthchecks. Monitor them with:
docker compose -f docker-compose.example.yml psHealthy services show (healthy) in the status column.
Ollama model setup
Section titled “Ollama model setup”After starting the stack, pull the embedding model:
docker compose -f docker-compose.example.yml exec ollama ollama pull bge-m3Optionally pull the LLM model:
docker compose -f docker-compose.example.yml exec ollama ollama pull mistralScaling considerations
Section titled “Scaling considerations”- SurrealDB: for high-throughput workloads, consider running SurrealDB on dedicated hardware with SSD storage.
- MeiliSearch: memory-mapped, so ensure sufficient RAM for the index size.
- Ollama: GPU acceleration is recommended for faster embeddings. Mount the GPU device in Docker Compose:
ollama: deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu]- PostgreSQL: default configuration is suitable for most workloads. Tune
shared_buffers,work_mem, andeffective_cache_sizefor large deployments.
Upgrading
Section titled “Upgrading”git pull origin maindocker compose -f docker-compose.example.yml builddocker compose -f docker-compose.example.yml up -dData volumes persist across upgrades. Check the CHANGELOG for any migration steps needed between versions.