Skip to content

Docker Compose Deployment

Audience: developer or operator deploying Cortex on a self-hosted server.


  • Docker >= 24
  • Docker Compose v2
  • ~4 GB RAM for the full stack
  • ~10 GB disk for initial deployment (grows with data)

Terminal window
git clone https://github.com/arka-labs/arka-cortex.git
cd arka-cortex
cp .env.example .env
# Edit .env — set all required secrets (see below)
docker compose -f docker-compose.example.yml up -d

Check health:

Terminal window
curl http://localhost:9096/health
# {"status":"ok"}

The docker-compose.example.yml defines 9 services:

ServiceImagePortPurpose
surrealdbsurrealdb/surrealdb:v2.1.78000Graph + relational database
postgrespostgres:16-alpine5432Audit trail, documents, auth
meilisearchgetmeili/meilisearch:v1.127700Full-text search
miniominio/minio:latest9000 (API), 9001 (console)S3-compatible object storage
minio-initminio/mc:latestInit container: creates the bucket
ollamaollama/ollama:latest11434Local embeddings + LLM
cortex_apicustom build9096REST API
cortex_mcpcustom build3001MCP server
cortex_uicustom build8080Web 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.


Edit your .env file with these required values:

Terminal window
# SurrealDB
SURREAL_USER=root
SURREAL_PASS=<strong-password>
# PostgreSQL
PG_PASSWORD=<strong-password>
# MeiliSearch
MEILI_MASTER_KEY=<strong-key-at-least-16-chars>
# MinIO
S3_ACCESS_KEY=<access-key>
S3_SECRET_KEY=<secret-key>
# Cortex
CORTEX_API_KEYS=<api-key-1>,<api-key-2>
ENCRYPTION_KEY=<32-bytes-base64>

Generate an encryption key:

Terminal window
openssl rand -base64 32

See the Environment Variables page for the full list.


Docker Compose uses healthchecks to enforce the startup order:

  1. surrealdb, postgres, meilisearch, minio, ollama start in parallel.
  2. minio-init waits for minio to be healthy, then creates the bucket and exits.
  3. cortex_api waits for all five backends to be healthy.
  4. cortex_mcp waits for cortex_api and ollama to be healthy.
  5. cortex_ui waits for cortex_api to start.

For production, place a reverse proxy in front of the stack for TLS termination. Example with Caddy:

# Caddyfile
cortex.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:

Terminal window
certbot --nginx -d cortex.example.com

SurrealDB stores data in a Docker volume (surreal_data). Export via the HTTP API:

Terminal window
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.

Terminal window
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:

Terminal window
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:

Terminal window
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)

A cron job running twice daily (03:00 and 13:00 UTC) is recommended:

Terminal window
0 3,13 * * * /opt/cortex/scripts/backup.sh >> /var/log/cortex-backup.log 2>&1

Terminal window
curl -sf http://localhost:9096/health
# {"status":"ok"}
Terminal window
curl -sf http://localhost:3001/health
# {"status":"ok"}
Terminal window
curl -H "Authorization: Bearer ${CORTEX_API_KEY}" \
http://localhost:9096/api/admin/status

Returns version, node/edge/document counts, and service health checks.

All services have built-in Docker healthchecks. Monitor them with:

Terminal window
docker compose -f docker-compose.example.yml ps

Healthy services show (healthy) in the status column.


After starting the stack, pull the embedding model:

Terminal window
docker compose -f docker-compose.example.yml exec ollama ollama pull bge-m3

Optionally pull the LLM model:

Terminal window
docker compose -f docker-compose.example.yml exec ollama ollama pull mistral

  • 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, and effective_cache_size for large deployments.

Terminal window
git pull origin main
docker compose -f docker-compose.example.yml build
docker compose -f docker-compose.example.yml up -d

Data volumes persist across upgrades. Check the CHANGELOG for any migration steps needed between versions.