Skip to content

Docker Compose

Docker Compose is the team profile default and the fastest way to get a complete MoE Sovereign stack running on a single host. The same docker-compose.yaml that the development team uses is production-ready.

Firewall is mandatory in production

The compose stack publishes ports 8002 (orchestrator), 8003 (MCP), 8088 (admin UI), 8098 (ChromaDB) and 3001 (Grafana) on 0.0.0.0 — they are reachable from any host that can route to this machine. Several of these endpoints have no authentication (e.g. POST /graph/knowledge/import) and assume network-level isolation. Configure your host firewall to expose only 80/443 to the public internet. See Firewall & Network Exposure for ready-to-run UFW / firewalld / iptables rules.

What the stack contains

flowchart TB
    subgraph CORE[Core services]
        O[langgraph-app<br/>:8002 → :8000]
        M[mcp-precision<br/>:8003]
        A[moe-admin<br/>:8088]
    end
    subgraph DATA[Data tier]
        PG[(terra_checkpoints<br/>postgres 17)]
        VK[(terra_cache<br/>valkey)]
        CH[(chromadb-vector<br/>:8001)]
        NJ[(neo4j-knowledge<br/>:7474/:7687)]
        KF[(moe-kafka<br/>KRaft :9092)]
    end
    subgraph OBS[Observability]
        PR[(moe-prometheus<br/>:9090)]
        GR[(moe-grafana<br/>:3001)]
        NE[(node-exporter)]
        CA[(cadvisor)]
    end
    subgraph EDGE[Edge]
        CD[moe-caddy<br/>:80/:443]
        DZ[moe-dozzle<br/>:9999]
    end

    CD --> O & A
    O --> PG & VK & CH & NJ & KF & M
    A --> PG & VK
    PR -- scrape --> O & NE & CA
    GR --> PR

    classDef core fill:#eef2ff,stroke:#6366f1;
    classDef data fill:#fef3c7,stroke:#d97706;
    classDef obs  fill:#ecfdf5,stroke:#059669;
    classDef edge fill:#fce7f3,stroke:#db2777;
    class O,M,A core;
    class PG,VK,CH,NJ,KF data;
    class PR,GR,NE,CA obs;
    class CD,DZ edge;

Optional components via Compose profiles

Three services are behind explicit profiles and only start when the profile is active. install.sh sets COMPOSE_PROFILES in .env and passes --profile <name> automatically based on your answers during setup.

Profile Services When to enable
neo4j neo4j-knowledge (~1.5 GB RAM) GraphRAG, ontology curator, knowledge graph
caddy moe-caddy Built-in TLS reverse proxy
authentik authentik-server, authentik-worker, authentik-postgresql, authentik-redis Self-hosted SSO/OIDC
# Core stack only (no Neo4j, no Caddy, no Authentik)
sudo docker compose up -d

# With Neo4j GraphRAG
sudo docker compose --profile neo4j up -d

# Full stack (Neo4j + Caddy + Authentik)
sudo docker compose --profile neo4j --profile caddy --profile authentik up -d

podman-compose

podman-compose does not auto-read COMPOSE_PROFILES from .env. Always pass --profile <name> explicitly on the command line.

Launch

Rebuilding after code changes

sudo docker compose build langgraph-app && sudo docker compose up -d langgraph-app
sudo docker compose build moe-admin    && sudo docker compose up -d moe-admin
sudo docker compose build mcp-precision && sudo docker compose up -d mcp-precision

The Dockerfiles are multi-stage, so rebuilds only redo the layers that actually changed — a typical main.py edit rebuilds in under 10 seconds.

Configuration via .env (no compose-file edits needed)

The shipped docker-compose.yml is fully parameterised. Every host path and every published port resolves through ${VAR:-default} so you can override them from .env without touching the compose file. This keeps git pull conflict-free.

Required passwords

Compose enforces these with ${VAR:?…}docker compose up aborts with an explicit error if any are missing. install.sh / bootstrap-macos.sh write all of them automatically.

.env key Used by Required
POSTGRES_CHECKPOINT_PASSWORD terra_checkpoints, langgraph-app, moe-admin Always
MOE_USERDB_PASSWORD langgraph-app, moe-admin Always
REDIS_PASSWORD terra_cache, langgraph-app, moe-admin Always
NEO4J_PASS neo4j-knowledge Only with neo4j profile — leave empty to disable GraphRAG
GF_SECURITY_ADMIN_PASSWORD moe-grafana Always
ADMIN_PASSWORD, ADMIN_SECRET_KEY moe-admin Always
AUTHENTIK_SECRET_KEY, AUTHENTIK_POSTGRESQL__PASSWORD authentik-* Only with authentik profile

Host data roots (bind-mount sources)

.env key Linux default macOS default Holds
MOE_DATA_ROOT /opt/moe-infra $HOME/moe-data Postgres, Neo4j, Redis, ChromaDB, Kafka, Prometheus, logs
GRAFANA_DATA_ROOT /opt/grafana $HOME/moe-grafana Grafana SQLite + dashboards
FEW_SHOT_HOST_DIR ${MOE_DATA_ROOT}/few-shot same Few-shot example store
CLAUDE_SKILLS_DIR ./skills ./skills Claude Code skill bundle

Published host ports (override to avoid collisions)

All ports are remappable. Container-internal ports stay the same; only the host side changes when you set <SERVICE>_HOST_PORT in .env.

Default .env key Service Bind
80 CADDY_HTTP_PORT Caddy reverse proxy 0.0.0.0
443 CADDY_HTTPS_PORT Caddy HTTPS / HTTP3 0.0.0.0
8002 LANGGRAPH_HOST_PORT Orchestrator API 0.0.0.0
8003 MCP_HOST_PORT MCP precision tools 0.0.0.0
8088 ADMIN_UI_HOST_PORT Admin UI 0.0.0.0
8098 DOCS_HOST_PORT Docs site 0.0.0.0
3001 GRAFANA_HOST_PORT Grafana 0.0.0.0
8001 CHROMA_HOST_PORT ChromaDB 127.0.0.1
9090 PROMETHEUS_HOST_PORT Prometheus 127.0.0.1
7474 NEO4J_HTTP_PORT Neo4j Browser 127.0.0.1
7687 NEO4J_BOLT_PORT Neo4j Bolt 127.0.0.1
9092 KAFKA_HOST_PORT Kafka broker 127.0.0.1
9999 DOZZLE_HOST_PORT Dozzle log viewer 127.0.0.1
9100 NODE_EXPORTER_HOST_PORT node-exporter 127.0.0.1
9338 CADVISOR_HOST_PORT cAdvisor 127.0.0.1

Example .env snippet to move the Admin UI off the default 8088 because your host already runs something there:

ADMIN_UI_HOST_PORT=8089

After editing .env, docker compose up -d re-creates only the affected container.

See .env.example for the full annotated reference and Firewall & Network Exposure for which services to firewall on each binding interface.