Last updated

Deployment

Mirrored from iblai/ontology ยท docs/deployment.md. This page is generated โ€” edit it in the repository, not here.

Part of the iblai-ontology architecture. See the architecture overview.

iblai-ontology deploys as a Docker Compose stack on university infrastructure. The stack contains only the knowledge layer โ€” the data stores, the MCP servers, the identity gateway, and the MCP exposure endpoint. The agent runtime is not part of the stack; it connects remotely over MCP (see platform-integration.md).


Prerequisites

The university provides:

  • A Linux server or VM inside their network โ€” Ubuntu 22.04+, 16 GB RAM minimum, 500 GB disk.
  • Docker + Docker Compose installed.
  • Network access from that server to the source systems: PeopleSoft Oracle (port 1521), and HTTPS to Canvas / Slate / Navigate APIs.
  • A read-only database user with SELECT grants on the relevant schemas (the safety suite will verify this).
  • A Canvas admin API token and Slate API credentials (as applicable).
  • An Entra ID app registration for iblai-ontology (see identity.md โ€” only basic auth scopes, no App Roles needed).
  • A DNS record pointing ontology.<university>.edu at the server.

The Stack

docker-compose.yml (knowledge layer only). Annotated and condensed; the canonical file is generated by ontology config init and extended by the provisioning engine.

services:
  # --- MCP Inbound: database connectivity (Component 1) ---
  mcp-toolbox:
    image: us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:1.5.0
    volumes: [./config/tools.yaml:/app/tools.yaml]
    env_file: .env.mcp
    networks: [ontology-internal]
    restart: unless-stopped

  # --- MCP Inbound: custom API servers (one per source) ---
  mcp-canvas:   { build: ./mcp-servers/canvas,   env_file: .env.canvas,   networks: [ontology-internal], restart: unless-stopped }
  mcp-slate:    { build: ./mcp-servers/slate,     env_file: .env.slate,    networks: [ontology-internal], restart: unless-stopped }
  mcp-navigate: { build: ./mcp-servers/navigate,  env_file: .env.navigate, networks: [ontology-internal], restart: unless-stopped }
  mcp-ldap:     { build: ./mcp-servers/ldap,      env_file: .env.ldap,     networks: [ontology-internal], restart: unless-stopped }

  # --- Knowledge store (Component 2) ---
  ontology-db:
    image: postgres:16-alpine
    volumes:
      - ontology-data:/var/lib/postgresql/data
      - ./sql/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql
      - ./sql/views.sql:/docker-entrypoint-initdb.d/02-views.sql
    environment:
      POSTGRES_DB: ontology
      POSTGRES_USER: ontology
      POSTGRES_PASSWORD: ${ONTOLOGY_DB_PASSWORD}
    networks: [ontology-internal]
    restart: unless-stopped

  vector-store:
    image: chromadb/chroma:0.6.3
    volumes: [vector-data:/chroma/chroma]
    environment:
      ANONYMIZED_TELEMETRY: "false"
      CHROMA_SERVER_AUTHN_PROVIDER: chromadb.auth.token_authn.TokenAuthenticationServerProvider
      CHROMA_SERVER_AUTHN_CREDENTIALS: ${CHROMA_TOKEN:?set CHROMA_TOKEN in .env}
    networks: [ontology-internal]
    restart: unless-stopped

  # --- Sync engine (Component 2; Django + Celery) ---
  sync-engine:
    build: ./sync-engine
    volumes:
      - ontology-files:/ontology
      - ./config/sync-schedules.yaml:/app/config.yaml
    environment:
      MCP_TOOLBOX_URL: http://mcp-toolbox:5000
      MCP_CANVAS_URL: http://mcp-canvas:3000
      ONTOLOGY_DB_URL: postgresql://ontology:${ONTOLOGY_DB_PASSWORD}@ontology-db:5432/ontology
      CHROMA_URL: http://vector-store:8000
      CHROMA_TOKEN: ${CHROMA_TOKEN:?set CHROMA_TOKEN in .env}
    depends_on: [mcp-toolbox, mcp-canvas, ontology-db, vector-store]
    networks: [ontology-internal]
    restart: unless-stopped

  # --- MCP Outbound: exposure gateway (Component 4) ---
  ontology-gateway:
    build: ./gateway
    volumes:
      - ontology-files:/ontology:ro          # read-only mount
      - ./config/roles.yaml:/app/roles.yaml
    environment:
      MCP_TOOLBOX_URL: http://mcp-toolbox:5000
      ONTOLOGY_DB_URL: postgresql://ontology:${ONTOLOGY_DB_PASSWORD}@ontology-db:5432/ontology
      CHROMA_URL: http://vector-store:8000
      CHROMA_TOKEN: ${CHROMA_TOKEN:?set CHROMA_TOKEN in .env}
      ENTRA_TENANT_ID: ${ENTRA_TENANT_ID}
      ENTRA_CLIENT_ID: ${ENTRA_CLIENT_ID}
    depends_on: [mcp-toolbox, ontology-db, vector-store]
    networks: [ontology-internal, ontology-exposed]   # bridges both networks
    restart: unless-stopped

  # --- HTTPS reverse proxy ---
  proxy:
    image: caddy:2-alpine
    volumes:
      - ./config/Caddyfile:/etc/caddy/Caddyfile
      - caddy-data:/data
    ports: ["443:443"]
    depends_on: [ontology-gateway]
    networks: [ontology-exposed]
    restart: unless-stopped

  # --- Admin dashboard ---
  admin-dashboard:
    build: ./admin-dashboard
    environment:
      ONTOLOGY_DB_URL: postgresql://ontology:${ONTOLOGY_DB_PASSWORD}@ontology-db:5432/ontology
      ENTRA_TENANT_ID: ${ENTRA_TENANT_ID}
      ENTRA_CLIENT_ID: ${ENTRA_CLIENT_ID}
    networks: [ontology-internal, ontology-exposed]
    restart: unless-stopped

networks:
  ontology-internal:
    driver: bridge
    internal: true            # No external/internet access for internal services
  ontology-exposed:
    driver: bridge            # Gateway and proxy can reach the outside

volumes:
  ontology-data:
  ontology-files:
  vector-data:
  caddy-data:

Internal vs. exposed networks

This is the heart of the on-prem security posture:

  • ontology-internal is marked internal: true โ€” containers on it cannot reach the internet. The databases, MCP Toolbox, custom MCP servers, and sync engine all live here only.
  • ontology-exposed is a normal bridge that can reach outside.
  • Only ontology-gateway and proxy bridge both networks. So PeopleSoft credentials, the cache, and the memory files are never directly exposed; the only door in is the gateway, behind the firewall + Entra-token gating described in Component 4.

Note also the gateway mounts ontology-files read-only (:ro) โ€” the exposure layer can serve memory files but never modify them.


Caddyfile

config/Caddyfile โ€” Caddy auto-provisions TLS via Let's Encrypt (or use a university-provided certificate):

ontology.alasu.edu {
    reverse_proxy ontology-gateway:8080
    log { output file /var/log/caddy/access.log }
}

admin.ontology.alasu.edu {
    reverse_proxy admin-dashboard:8080
    log { output file /var/log/caddy/admin-access.log }
}

Environment File

Credentials are split per MCP server for isolation โ€” each container loads only its own file via env_file:. Compose will not parse unless every referenced file exists, and each custom MCP server does os.environ["X"] at import, so a missing/empty file crash-loops that one container. Templates for all of them ship as .env.*.example (copy each to the real name; the real files are git-ignored):

FileVarsContainer
.envONTOLOGY_DB_PASSWORD, ENTRA_TENANT_ID, ENTRA_CLIENT_IDcompose ${...} + gateway/admin
.env.mcpPEOPLESOFT_DB_HOST/PORT/NAME, PEOPLESOFT_RO_USER/PASSWORD, ONTOLOGY_DB_USER, ONTOLOGY_DB_PASSWORDmcp-toolbox
.env.canvasCANVAS_BASE_URL, CANVAS_API_TOKENmcp-canvas
.env.slateSLATE_BASE_URL, SLATE_API_KEYmcp-slate
.env.navigateNAVIGATE_BASE_URL, NAVIGATE_API_KEYmcp-navigate
.env.ldapLDAP_URI, LDAP_BIND_DN, LDAP_BIND_PASSWORD, LDAP_BASE_DNmcp-ldap

CANVAS_BASE_URL is the instance root only โ€” no trailing slash, no /api/v1 (the server appends it). The Canvas token must be admin-scoped to look up other users; the tools accept a Canvas user id, a SIS id, or a sis_login_id: ref.

Cloud sources note: ontology-internal is internal: true (no internet egress). A cloud source (e.g. *.instructure.com) is unreachable from a container placed only on that network โ€” reach it from an egress-capable network, or test the MCP server standalone.

Representative .env values:

# PeopleSoft Oracle (read-only credentials provided by the university)
PEOPLESOFT_DB_HOST=psft-db.internal.alasu.edu
PEOPLESOFT_DB_PORT=1521
PEOPLESOFT_DB_NAME=CSPRD
PEOPLESOFT_RO_USER=iblai_readonly
PEOPLESOFT_RO_PASSWORD=<provided-by-university>

# Canvas LMS
CANVAS_BASE_URL=https://alasu.instructure.com
CANVAS_API_TOKEN=<generated-by-canvas-admin>

# Slate CRM
SLATE_BASE_URL=https://apply.alasu.edu
SLATE_API_KEY=<provided-by-slate-admin>

# Local iblai-ontology DB
ONTOLOGY_DB_PASSWORD=<generated-during-setup>

# Microsoft Entra ID
ENTRA_TENANT_ID=<university-tenant-id>
ENTRA_CLIENT_ID=<iblai-ontology-app-client-id>

# Credential encryption (Fernet) for the service registry
ONTOLOGY_CREDENTIAL_KEY=<generated-during-setup>

MCP Toolbox config generation

The mcp-toolbox container does not read config/tools.yaml directly โ€” that file is the ontology's authoring DSL (kind: source|tool|toolset documents). The Toolbox needs its own native format (top-level sources / tools / toolsets maps). ontology mcp build translates the DSL into config/generated/toolbox.yaml (git-ignored), which the container mounts:

ontology mcp build          # writes config/generated/toolbox.yaml
# ontology deploy up runs this automatically before starting the stack

Translation rules: type โ†’ kind; Oracle's database โ†’ serviceName; ${VAR} tokens are left intact so the Toolbox expands them from .env.mcp (secrets never land in the generated file). Tools whose statement is a ${...} raw-SQL passthrough (e.g. query-ontology-cache) can't be expressed as a Toolbox parameterized query โ€” they're served directly by the gateway and excluded from the generated file. The gateway calls tools via the Toolbox /mcp JSON-RPC endpoint (the legacy /api/tool/<name> REST path is disabled in Toolbox โ‰ฅ1.5).

Eager connection โ€” important. The Toolbox connects to every source at startup and refuses to start if any source is unreachable. So the active tools.yaml must contain only sources you have actually configured and can reach. For this reason the higher-ed PeopleSoft sample is kept out of the default tools.yaml โ€” it lives in config/tools.higher-ed.example.yaml (paired with config/roles.higher-ed.example.yaml). Merge it in only when you have a real PeopleSoft to point at; otherwise the mcp-toolbox container crash-loops.

Reaching source databases (local or remote)

Source connections (client-postgres, client-mysql, โ€ฆ) are host/credential-driven via .env.mcp, so the same config connects to a local container or a remote host โ€” only the network path differs. That path is deployment-specific and lives in a git-ignored docker-compose.override.yml (copy from docker-compose.override.example.yml), not the committed compose:

  • Local source containers: add mcp-toolbox to the source's docker network and set *_HOST to the container name (e.g. my-postgres, my-mysql).
  • Remote sources: ontology-internal is internal: true (no egress), so add mcp-toolbox to an egress-capable network and set *_HOST to the remote hostname/IP.

Operating the Stack

The ontology deploy commands wrap Docker Compose (see CLI reference):

ontology deploy up --build        # start everything
ontology deploy status            # container status
ontology deploy logs sync-engine -f --tail 20
ontology deploy restart ontology-gateway
ontology deploy down              # stop (add --volumes to wipe data)
$ ontology deploy status
NAME                STATUS    PORTS
ontology-db         running   5432/tcp
vector-store        running   8000/tcp
mcp-toolbox         running   5000/tcp
mcp-canvas          running   3000/tcp
sync-engine         running
ontology-gateway    running   8080/tcp
proxy               running   0.0.0.0:443->443/tcp
admin-dashboard     running   8080/tcp

Validate the deployment with ontology health and ontology config validate.


Phased Rollout

Phase 1 โ€” Foundation (Weeks 1โ€“3)

Week 1 โ€” Infrastructure and first source.

  1. Clone the deployment repo onto the server; cp .env.example .env and fill it in.
  2. Start core infra: docker compose up -d ontology-db vector-store mcp-toolbox.
  3. Verify Toolbox reaches PeopleSoft (call get-student-enrollment).
  4. Run the initial full sync: docker compose up -d sync-engine; watch logs.
  5. Verify memory files and the Postgres cache populated (SELECT COUNT(*) FROM students;).

Week 2 โ€” Exposure and authentication. 6. Configure the Entra ID app registration (identity.md). 7. Start the gateway + proxy: docker compose up -d ontology-gateway proxy. 8. Test token validation with a real Entra token (device-code flow), then verify role scoping: a financial-aid counselor sees finaid + enrollment tools; a student sees only self-service tools; an unassigned user gets the default role (or 403 where applicable).

Week 3 โ€” Connect the agent runtime and validate end-to-end. 9. Register iblai-ontology as an MCP Server in the ibl.ai platform and attach it to agents (platform-integration.md). 10. Drive an end-to-end agent interaction: user opens chat โ†’ platform triggers in-chat OAuth โ†’ user authenticates โ†’ platform creates ConnectedService + MCPServerConnection โ†’ agent queries the gateway with the user's token โ†’ scoped data returned. 11. Deploy the admin dashboard; cross-check ontology data against PeopleSoft reports (counts, GPAs, holds).

Phase 2 โ€” Expand sources (Weeks 3โ€“6)

  1. Deploy the Canvas and Slate MCP servers (docker compose up -d mcp-canvas mcp-slate).
  2. Extend sync schedules so student memory files include Canvas grades and Slate admissions data.
  3. Build and test the Financial Aid Agent and the Advising Agent.
  4. Verify role-based boundaries: a counselor sees aid data but not HR; a student sees only their own record.

Phase 3 โ€” Full iblai-ontology (Weeks 6โ€“10)

  1. Add the remaining MCP servers: Navigate, LDAP/AD, and others.
  2. Build department agents (IT Help Desk, Facilities, HR).
  3. Enable vector search over text memories via ChromaDB.
  4. Build out the admin dashboard (sync monitoring, audit-log viewer, role management).
  5. Production-harden: TLS certificates, backup schedules, monitoring/alerting, log aggregation.
  6. Load test concurrent users across roles; verify response times and source-system impact.

Phase 4 โ€” Advanced (ongoing)

  1. Event-driven sync via webhooks from PeopleSoft / Canvas / Slate for real-time updates to critical data (new holds, registration changes, grade submissions).
  2. Student-facing self-service agents with limited scope.
  3. Cross-institutional templates โ€” package the stack so a new deployment is clone โ†’ fill credentials โ†’ docker compose up.
  4. Write-back with approval workflows โ€” an agent creates a pending action, a human approves in the UI, and the approved action executes via an MCP write tool.

Copyright ยฉ ibl.ai | support@iblai.zendesk.com