name: itworx-mcp-hub x-owned-service: &owned-service restart: unless-stopped read_only: true cap_drop: ["ALL"] security_opt: ["no-new-privileges:true"] tmpfs: ["/tmp:size=64m,mode=1777"] pids_limit: 128 mem_limit: 512m cpus: 1.0 logging: driver: json-file options: {max-size: "10m", max-file: "3"} healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/healthz', timeout=2)"] interval: 60s timeout: 3s retries: 5 start_period: 10s x-third-party: &third-party restart: unless-stopped security_opt: ["no-new-privileges:true"] logging: driver: json-file options: {max-size: "10m", max-file: "3"} services: postgres: <<: *third-party image: gitea.example.com/jens/itworx-mcp-hub/postgres:3237e4d8aec312d63d13b4e80664422a7520b5f9-security-run-23280-attempt-1@sha256:4e471418b9cf0805ed3c5cce716fb13ac96aa1fcabb6751fd4386b1db679e176 entrypoint: ["sh", "/opt/itworx/postgres-entrypoint.sh"] command: ["postgres"] environment: POSTGRES_USER: postgres POSTGRES_PASSWORD_FILE: /run/secrets/postgres_superuser_password PGDATA: /var/lib/postgresql/data/pgdata SECRET_EXPECTED_UID: "${PUID:-1000}" SECRET_EXPECTED_GID: "${PGID:-1000}" secrets: [postgres_superuser_password, hub_database_password, contextforge_database_password] volumes: - postgres-data:/var/lib/postgresql/data - ../postgres/init:/docker-entrypoint-initdb.d:ro - ../images/postgres/entrypoint.sh:/opt/itworx/postgres-entrypoint.sh:ro tmpfs: ["/run/itworx-secrets:size=1m,mode=0700"] networks: [hub-data] healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"] interval: 15s timeout: 5s retries: 5 redis: <<: *third-party image: gitea.example.com/jens/itworx-mcp-hub/redis:wp400-remediation-20261002-4e18f61c@sha256:7d826b44f245ad32e8fe4bf40d42eae95855c3942c5f2e42784ee8fd3f9547a2 entrypoint: ["sh", "/opt/itworx/redis-entrypoint.sh"] environment: REDIS_AUTH_ENABLED: "${REDIS_AUTH_ENABLED:-false}" SECRET_EXPECTED_UID: "${PUID:-1000}" SECRET_EXPECTED_GID: "${PGID:-1000}" volumes: - ../images/redis/entrypoint.sh:/opt/itworx/redis-entrypoint.sh:ro - ../images/redis/healthcheck.sh:/opt/itworx/redis-healthcheck.sh:ro secrets: [redis_password] tmpfs: ["/run/itworx:size=1m,mode=0700"] networks: [hub-data] healthcheck: test: ["CMD", "sh", "/opt/itworx/redis-healthcheck.sh"] interval: 15s timeout: 3s retries: 5 contextforge: <<: *third-party image: ghcr.io/ibm/mcp-context-forge:v1.0.6@sha256:049b8e61fa1187f72773d94bcf91fc63338f4fea7808949a8a5daacbc89ef92e user: "${PUID:-1000}:${PGID:-1000}" entrypoint: ["sh", "/opt/itworx/contextforge-entrypoint.sh"] environment: HOST: 0.0.0.0 PORT: 4444 AUTH_REQUIRED: "true" MCPGATEWAY_UI_ENABLED: "false" MCPGATEWAY_ADMIN_API_ENABLED: "false" MCPGATEWAY_MCP_APPS_ENABLED: "true" ENABLE_HEADER_PASSTHROUGH: "true" API_ALLOW_BASIC_AUTH: "false" PLATFORM_ADMIN_EMAIL: "${CONTEXTFORGE_PLATFORM_ADMIN_EMAIL:-contextforge-admin@example.com}" SSRF_PROTECTION_ENABLED: "true" SSRF_ALLOW_PRIVATE_NETWORKS: "false" SSRF_DNS_FAIL_CLOSED: "true" SSRF_ALLOWED_NETWORKS: '["203.0.113.0/24"]' UAID_ALLOWED_DOMAINS: '["example.com"]' GUNICORN_WORKERS: "2" # ContextForge only receives authenticated internal Hub traffic. Its # generic limiter groups every caller behind the router's single IP and # can lock out all ChatGPT sessions at once. Admission control therefore # belongs to the public dataplane router below. RATE_LIMITING_ENABLED: "false" REDIS_AUTH_ENABLED: "${REDIS_AUTH_ENABLED:-false}" SECRET_EXPECTED_UID: "${PUID:-1000}" SECRET_EXPECTED_GID: "${PGID:-1000}" volumes: - ../images/contextforge/entrypoint.sh:/opt/itworx/contextforge-entrypoint.sh:ro secrets: [contextforge_database_password, contextforge_auth_encryption_secret, contextforge_jwt_secret, contextforge_admin_credential, redis_password] depends_on: postgres: {condition: service_healthy} redis: {condition: service_healthy} networks: hub-control: aliases: [contextforge-control] hub-connectors: {} hub-data: {} hub-observe: {} healthcheck: test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:4444/health', timeout=3)\""] interval: 60s timeout: 10s retries: 5 start_period: 120s control-api: <<: *owned-service build: {context: ../.., dockerfile: infrastructure/images/python-service/Dockerfile, args: {SERVICE_NAME: control-api}} image: local/itworx-mcp-hub-control:dev environment: HUB_OIDC_ISSUER: "${HUB_OIDC_ISSUER:-https://auth.dev.invalid/application/o/itworx/}" HUB_OIDC_AUDIENCE: "${HUB_OIDC_AUDIENCE:-itworx-mcp-hub}" HUB_OIDC_CLIENT_ID: "${HUB_OIDC_CLIENT_ID:-itworx-mcp-hub-dev}" HUB_OIDC_REDIRECT_URI: "${HUB_OIDC_REDIRECT_URI:-https://hub.dev.invalid/auth/callback}" HUB_OIDC_CLIENT_SECRET_FILE: /run/secrets/authentik_oidc_client_secret HUB_OIDC_ROLE_GROUP_MAP: '${HUB_OIDC_ROLE_GROUP_MAP:-{"hub-admin":"admin"}}' HUB_SESSION_SIGNING_KEY_FILE: /run/secrets/hub_session_signing_key HUB_CONTEXTFORGE_URL: "${HUB_CONTEXTFORGE_URL:-http://contextforge-control:4444}" HUB_CONTEXTFORGE_TOKEN_FILE: /run/secrets/contextforge_api_token HUB_CONTEXTFORGE_AUTH_SCHEME: Bearer HUB_DATABASE_PASSWORD_FILE: /run/secrets/hub_database_password HUB_DATABASE_HOST: postgres HUB_DATABASE_NAME: itworx_mcp_hub HUB_DATABASE_USER: itworx_mcp_hub HUB_CATALOG_ROOT: /opt/hub/catalog INGESTION_CONNECTOR_ENABLED: "${INGESTION_CONNECTOR_ENABLED:-false}" HUB_PUBLIC_MCP_URL: "${HUB_PUBLIC_MCP_URL:-https://mcp.example.com}" HUB_BACKUP_STATUS_FILE: /var/lib/itworx/backup-status.json HUB_BACKUP_STALE_AFTER_HOURS: "${HUB_BACKUP_STALE_AFTER_HOURS:-26}" CONNECTOR_IDENTITY_KEY_FILE: /run/secrets/connector_identity_signing_key REQUIRED_SECRET_FILES: /run/secrets/authentik_oidc_client_secret,/run/secrets/hub_session_signing_key,/run/secrets/contextforge_api_token,/run/secrets/hub_database_password,/run/secrets/connector_identity_signing_key SECRET_EXPECTED_UID: "${PUID:-10001}" SECRET_EXPECTED_GID: "${PGID:-10001}" secrets: [authentik_oidc_client_secret, hub_session_signing_key, contextforge_api_token, hub_database_password, connector_identity_signing_key] volumes: - "${BACKUP_STATUS_FILE_HOST:-../../examples/backup-status.json}:/var/lib/itworx/backup-status.json:ro" - "../../catalog:/opt/hub/catalog:ro" depends_on: postgres: {condition: service_healthy} redis: {condition: service_healthy} contextforge: {condition: service_healthy} networks: [hub-control, hub-connectors, hub-data, hub-observe, hub-egress] worker: <<: *owned-service user: "${PUID:-10001}:${PGID:-10001}" build: {context: ../.., dockerfile: infrastructure/images/python-service/Dockerfile, args: {SERVICE_NAME: worker}} image: local/itworx-mcp-hub-worker:dev environment: HUB_OIDC_ISSUER: "${HUB_OIDC_ISSUER:-https://auth.dev.invalid/application/o/itworx/}" HUB_CONTEXTFORGE_URL: "${HUB_CONTEXTFORGE_URL:-http://contextforge-control:4444}" HUB_CONTEXTFORGE_TOKEN_FILE: /run/secrets/contextforge_api_token HUB_CONTEXTFORGE_AUTH_SCHEME: Bearer HUB_DATABASE_PASSWORD_FILE: /run/secrets/hub_database_password HUB_DATABASE_HOST: postgres HUB_DATABASE_NAME: itworx_mcp_hub HUB_DATABASE_USER: itworx_mcp_hub HUB_CATALOG_ROOT: /opt/hub/catalog INGESTION_CONNECTOR_ENABLED: "${INGESTION_CONNECTOR_ENABLED:-false}" MCP_CLIENT_BINDINGS_JSON: '${MCP_CLIENT_BINDINGS_JSON:-[]}' REQUIRED_SECRET_FILES: /run/secrets/hub_database_password,/run/secrets/contextforge_api_token SECRET_EXPECTED_UID: "${PUID:-10001}" SECRET_EXPECTED_GID: "${PGID:-10001}" secrets: [hub_database_password, contextforge_api_token] volumes: - "../../catalog:/opt/hub/catalog:ro" - "../../services/runtime/state_sync.py:/opt/hub/services/runtime/state_sync.py:ro" depends_on: control-api: {condition: service_healthy} redis: {condition: service_healthy} connector-knowledge: {condition: service_healthy} connector-gitea: {condition: service_healthy} connector-unraid: {condition: service_healthy} connector-mobilityops: {condition: service_healthy} connector-projectbrain: {condition: service_healthy} connector-appops: {condition: service_healthy} connector-stitch: {condition: service_healthy} networks: [hub-control, hub-connectors, hub-data, hub-observe] healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health/ready', timeout=2)"] interval: 10s timeout: 3s retries: 18 start_period: 10s dataplane-router: <<: *owned-service # Docker bind-mounted secret files retain the Unraid PUID/PGID. Run the # router as that already unprivileged identity so its fail-closed secret # validation can read files that remain owner-only (0600). user: "${PUID:-10001}:${PGID:-10001}" build: {context: ../.., dockerfile: infrastructure/images/python-service/Dockerfile, args: {SERVICE_NAME: dataplane-router}} image: local/itworx-mcp-hub-dataplane:dev environment: HUB_OIDC_ISSUER: "${HUB_OIDC_ISSUER:-https://auth.dev.invalid/application/o/itworx/}" HUB_OIDC_AUDIENCE: "${HUB_OIDC_AUDIENCE:-itworx-mcp-hub}" HUB_OIDC_CLIENT_ID: "${HUB_OIDC_CLIENT_ID:-itworx-mcp-hub-dev}" HUB_OIDC_REDIRECT_URI: "${HUB_OIDC_REDIRECT_URI:-https://hub.dev.invalid/auth/callback}" HUB_MCP_ADDITIONAL_OIDC_ISSUER: "${HUB_MCP_ADDITIONAL_OIDC_ISSUER:-}" HUB_MCP_ADDITIONAL_OIDC_AUDIENCE: "${HUB_MCP_ADDITIONAL_OIDC_AUDIENCE:-}" HUB_MCP_ADDITIONAL_OIDC_CLIENT_ID: "${HUB_MCP_ADDITIONAL_OIDC_CLIENT_ID:-}" HUB_MCP_ADDITIONAL_OIDC_REDIRECT_URI: "${HUB_MCP_ADDITIONAL_OIDC_REDIRECT_URI:-}" HUB_PUBLIC_MCP_URL: "${HUB_PUBLIC_MCP_URL:-https://mcp.example.com}" HUB_CONTEXTFORGE_URL: "${HUB_CONTEXTFORGE_URL:-http://contextforge-control:4444}" HUB_CONTEXTFORGE_TOKEN_FILE: /run/secrets/contextforge_api_token HUB_CONTEXTFORGE_AUTH_SCHEME: Bearer HUB_DATABASE_PASSWORD_FILE: /run/secrets/hub_database_password HUB_DATABASE_HOST: postgres HUB_DATABASE_NAME: itworx_mcp_hub HUB_DATABASE_USER: itworx_mcp_hub HUB_CATALOG_ROOT: /opt/hub/catalog HUB_RATE_LIMIT_PER_MINUTE: "${HUB_RATE_LIMIT_PER_MINUTE:-600}" HUB_UPSTREAM_MAX_CONCURRENCY: "${HUB_UPSTREAM_MAX_CONCURRENCY:-24}" HUB_UPSTREAM_QUEUE_TIMEOUT_SECONDS: "${HUB_UPSTREAM_QUEUE_TIMEOUT_SECONDS:-5}" CONNECTOR_IDENTITY_KEY_FILE: /run/secrets/connector_identity_signing_key INGESTION_CONNECTOR_ENABLED: "${INGESTION_CONNECTOR_ENABLED:-false}" DEMO_TOKEN_KEY_FILE: /run/secrets/demo_token_signing_key DEMO_TOKEN_REVOCATIONS_FILE: /etc/itworx/demo-token-revocations.json DEMO_TOKEN_RATE_LIMIT_PER_MINUTE: "${DEMO_TOKEN_RATE_LIMIT_PER_MINUTE:-10}" REQUIRED_SECRET_FILES: /run/secrets/contextforge_api_token,/run/secrets/hub_database_password,/run/secrets/connector_identity_signing_key,/run/secrets/demo_token_signing_key SECRET_EXPECTED_UID: "${PUID:-10001}" SECRET_EXPECTED_GID: "${PGID:-10001}" secrets: [contextforge_api_token, hub_database_password, connector_identity_signing_key, demo_token_signing_key] volumes: - "../../catalog:/opt/hub/catalog:ro" - "../../services/runtime/dataplane.py:/opt/hub/services/runtime/dataplane.py:ro" # Never silently fall back to the repository example: that would make # the operator's early-revocation file ineffective. # Mount the containing directory: the operator verifier atomically replaces # the file and therefore needs a writable parent directory in its profile. - "${DEMO_TOKEN_REVOCATIONS_DIR_HOST:?Set the host-side demo-token revocations directory}:/etc/itworx:ro" depends_on: {postgres: {condition: service_healthy}, contextforge: {condition: service_healthy}} networks: [hub-edge, hub-control, hub-data, hub-observe, hub-egress] ports: ["${MCP_BIND_IP:-127.0.0.1}:${MCP_PORT:-18442}:8080"] operator-tools: # Explicit one-shot profile for operator-only proofs. It receives no Docker # socket and does not start as part of the normal application topology. image: local/itworx-mcp-hub-dataplane:dev profiles: [operator-tools] user: "${PUID:-10001}:${PGID:-10001}" working_dir: /opt/hub entrypoint: ["python", "-m", "scripts.verify_demo_token_flow"] secrets: [demo_token_signing_key] volumes: - "${DEMO_TOKEN_REVOCATIONS_DIR_HOST:?Set the host-side demo-token revocations directory}:/etc/itworx:rw" networks: [hub-edge] web: <<: *owned-service build: {context: ../.., dockerfile: infrastructure/images/python-service/Dockerfile, args: {SERVICE_NAME: web}} image: local/itworx-mcp-hub-web:dev environment: HUB_PUBLIC_MCP_URL: "${HUB_PUBLIC_MCP_URL:-https://mcp.example.com}" HUB_MCP_ADDITIONAL_OIDC_ISSUER: "${HUB_MCP_ADDITIONAL_OIDC_ISSUER:-https://auth.example-host.com/application/o/chatgpt-itworx-gitea/}" HUB_WORKER_URL: "http://worker:8080" volumes: - "../../services/runtime/web.py:/opt/hub/services/runtime/web.py:ro" depends_on: control-api: {condition: service_healthy} worker: {condition: service_healthy} networks: [hub-edge, hub-control, hub-observe] ports: ["${HUB_BIND_IP:-127.0.0.1}:${HUB_PORT:-1100}:8080"] connector-knowledge: <<: *owned-service build: {context: ../.., dockerfile: infrastructure/images/python-service/Dockerfile, args: {SERVICE_NAME: connector-knowledge}} image: local/itworx-mcp-hub-connector-knowledge:dev volumes: - "${PROJECTS_READONLY_ROOT:-../../}:/sources:ro" environment: KNOWLEDGE_SOURCES_JSON: '${KNOWLEDGE_SOURCES_JSON:-[]}' CONNECTOR_IDENTITY_KEY_FILE: /run/secrets/connector_identity_signing_key REQUIRED_SECRET_FILES: /run/secrets/connector_identity_signing_key SECRET_EXPECTED_UID: "${PUID:-10001}" SECRET_EXPECTED_GID: "${PGID:-10001}" secrets: [connector_identity_signing_key] networks: [hub-connectors, hub-observe] connector-ingestion: <<: *owned-service profiles: [knowledge-intake] # Keep /tmp writable to the non-root connector user; each intake job creates # its own 0700 directory and 0600 file inside this shared sticky tmpfs. tmpfs: ["/tmp:size=128m,mode=1777"] build: {context: ../.., dockerfile: infrastructure/images/python-service/Dockerfile, args: {SERVICE_NAME: connector-ingestion}} image: local/itworx-mcp-hub-connector-ingestion:dev environment: KNOWLEDGE_HUB_ENDPOINT: "${KNOWLEDGE_HUB_ENDPOINT:-https://knowledge-hub.invalid}" KNOWLEDGE_HUB_ALLOWED_PROJECT_IDS: "${KNOWLEDGE_HUB_ALLOWED_PROJECT_IDS:-}" KNOWLEDGE_HUB_WORKSPACE_READ_ENABLED: "${KNOWLEDGE_HUB_WORKSPACE_READ_ENABLED:-false}" KNOWLEDGE_HUB_INGESTION_TOKEN_FILE: /run/secrets/knowledge_hub_ingestion_token INGESTION_SCANNER_HOST: ingestion-clamd INGESTION_SCANNER_PORT: "3310" KNOWLEDGE_HUB_INGESTION_IDENTITY_KEY_FILE: /run/secrets/knowledge_hub_ingestion_identity_key REQUIRED_SECRET_FILES: /run/secrets/knowledge_hub_ingestion_token,/run/secrets/knowledge_hub_ingestion_identity_key SECRET_EXPECTED_UID: "${PUID:-10001}" SECRET_EXPECTED_GID: "${PGID:-10001}" secrets: [knowledge_hub_ingestion_token, knowledge_hub_ingestion_identity_key] depends_on: {ingestion-clamd: {condition: service_started}} networks: [hub-connectors, hub-observe, hub-egress] ingestion-clamd: <<: *third-party profiles: [knowledge-intake] image: gitea.example.com/jens/itworx-mcp-hub/ingestion-clamd:3237e4d8aec312d63d13b4e80664422a7520b5f9-security-run-23280-attempt-1@sha256:eeca204bb48b30e78cf103bff00a33babb45e7c289747d7469396551803a8d23 cap_drop: ["ALL"] # /init must chown its signature volume, prepare log files, and drop to clamav. cap_add: ["CHOWN", "DAC_OVERRIDE", "FOWNER", "SETUID", "SETGID"] mem_limit: 3g pids_limit: 128 volumes: ["ingestion-clamav-db:/var/lib/clamav"] networks: [hub-connectors, hub-egress] connector-gitea: <<: *owned-service tmpfs: ["/tmp:size=512m,mode=1777"] build: {context: ../.., dockerfile: infrastructure/images/python-service/Dockerfile, args: {SERVICE_NAME: connector-gitea}} image: local/itworx-mcp-hub-connector-gitea:dev environment: GITEA_CONNECTOR_BUILD_SHA: "${GITEA_COMMIT_SHA:-unknown}" GITEA_ENDPOINT: "${GITEA_ENDPOINT:-https://gitea.dev.invalid}" GITEA_TOKEN_FILE: /run/secrets/gitea_mcp_token GITEA_ENABLE_PR_WRITES: "${GITEA_ENABLE_PR_WRITES:-false}" GITEA_WRITE_TOKEN_FILE: /run/secrets/gitea_mcp_write_token GITEA_WRITE_OWNER: '${GITEA_WRITE_OWNER:-}' GITEA_REPOSITORIES_JSON: '${GITEA_REPOSITORIES_JSON:-[{"project":"dev","owner":"dev","name":"dev"}]}' GITEA_ADDITIONAL_PROJECTS: '${GITEA_ADDITIONAL_PROJECTS:-gitea-loop}' GITEA_AUTO_DISCOVER_PROJECT: '${GITEA_AUTO_DISCOVER_PROJECT:-}' CONNECTOR_IDENTITY_KEY_FILE: /run/secrets/connector_identity_signing_key REQUIRED_SECRET_FILES: /run/secrets/gitea_mcp_token,/run/secrets/gitea_mcp_write_token,/run/secrets/connector_identity_signing_key SECRET_EXPECTED_UID: "${PUID:-10001}" SECRET_EXPECTED_GID: "${PGID:-10001}" secrets: [gitea_mcp_token, gitea_mcp_write_token, connector_identity_signing_key] volumes: - "../../services/runtime/connector_gitea.py:/opt/hub/services/runtime/connector_gitea.py:ro" depends_on: {contextforge: {condition: service_healthy}} networks: [hub-connectors, hub-observe, hub-egress] connector-mobilityops: <<: *owned-service build: {context: ../.., dockerfile: infrastructure/images/python-service/Dockerfile, args: {SERVICE_NAME: connector-mobilityops}} image: local/itworx-mcp-hub-connector-mobilityops:dev environment: MOBILITYOPS_ENDPOINT: "${MOBILITYOPS_ENDPOINT:-https://mobilityops.dev.invalid}" MOBILITYOPS_SERVICE_TOKEN_FILE: /run/secrets/mobilityops_service_token MOBILITYOPS_PROJECTS_JSON: '${MOBILITYOPS_PROJECTS_JSON:-["mobilityops"]}' CONNECTOR_IDENTITY_KEY_FILE: /run/secrets/connector_identity_signing_key REQUIRED_SECRET_FILES: /run/secrets/mobilityops_service_token,/run/secrets/connector_identity_signing_key SECRET_EXPECTED_UID: "${PUID:-10001}" SECRET_EXPECTED_GID: "${PGID:-10001}" secrets: [mobilityops_service_token, connector_identity_signing_key] depends_on: {contextforge: {condition: service_healthy}} networks: [hub-connectors, hub-observe, hub-egress] connector-projectbrain: <<: *owned-service build: {context: ../.., dockerfile: infrastructure/images/python-service/Dockerfile, args: {SERVICE_NAME: connector-projectbrain}} image: local/itworx-mcp-hub-connector-projectbrain:dev environment: PROJECTBRAIN_ENDPOINT: "${PROJECTBRAIN_ENDPOINT:-https://projectbrain.dev.invalid}" PROJECTBRAIN_PROJECTS: "${PROJECTBRAIN_PROJECTS:-gitea-all},gitea-analysis,gitea-loop" PROJECTBRAIN_SERVICE_TOKEN_FILE: /run/secrets/projectbrain_hub_service_token CONNECTOR_IDENTITY_KEY_FILE: /run/secrets/connector_identity_signing_key REQUIRED_SECRET_FILES: /run/secrets/projectbrain_hub_service_token,/run/secrets/connector_identity_signing_key SECRET_EXPECTED_UID: "${PUID:-10001}" SECRET_EXPECTED_GID: "${PGID:-10001}" secrets: [projectbrain_hub_service_token, connector_identity_signing_key] depends_on: {contextforge: {condition: service_healthy}} networks: [hub-connectors, hub-observe, hub-egress] connector-appops: <<: *owned-service build: {context: ../.., dockerfile: infrastructure/images/python-service/Dockerfile, args: {SERVICE_NAME: connector-appops}} image: local/itworx-mcp-hub-connector-appops:dev environment: APPOPS_ENDPOINT: "${APPOPS_ENDPOINT:-http://198.18.10.150:1216}" APPOPS_PROJECTS: "${APPOPS_PROJECTS:-server-ops}" APPOPS_SERVICE_TOKEN_FILE: /run/secrets/appops_hub_service_token CONNECTOR_IDENTITY_KEY_FILE: /run/secrets/connector_identity_signing_key REQUIRED_SECRET_FILES: /run/secrets/appops_hub_service_token,/run/secrets/connector_identity_signing_key SECRET_EXPECTED_UID: "${PUID:-10001}" SECRET_EXPECTED_GID: "${PGID:-10001}" secrets: [appops_hub_service_token, connector_identity_signing_key] depends_on: {contextforge: {condition: service_healthy}} networks: [hub-connectors, hub-observe, hub-egress] connector-stitch: <<: *owned-service build: {context: ../.., dockerfile: infrastructure/images/python-service/Dockerfile, args: {SERVICE_NAME: connector-stitch}} image: local/itworx-mcp-hub-connector-stitch:dev environment: STITCH_ENDPOINT: "https://stitch.googleapis.com/mcp" STITCH_HUB_PROJECTS: "${STITCH_HUB_PROJECTS:-stitch-design}" STITCH_API_KEY_FILE: /run/secrets/stitch_api_key CONNECTOR_IDENTITY_KEY_FILE: /run/secrets/connector_identity_signing_key REQUIRED_SECRET_FILES: /run/secrets/stitch_api_key,/run/secrets/connector_identity_signing_key SECRET_EXPECTED_UID: "${PUID:-10001}" SECRET_EXPECTED_GID: "${PGID:-10001}" secrets: [stitch_api_key, connector_identity_signing_key] depends_on: {contextforge: {condition: service_healthy}} networks: [hub-connectors, hub-observe, hub-egress] docker-read-broker: <<: *owned-service build: {context: ../.., dockerfile: infrastructure/images/docker-read-broker/Dockerfile} image: local/itworx-mcp-hub-docker-read-broker:dev environment: OTEL_EXPORTER_OTLP_ENDPOINT: http://observability-collector:4318 BROKER_TOKEN_FILE: /run/secrets/docker_broker_service_token BROKER_REGISTRATIONS_FILE: /etc/itworx/registered-targets.json SECRET_EXPECTED_UID: "${PUID:-10002}" SECRET_EXPECTED_GID: "${PGID:-10002}" secrets: [docker_broker_service_token] group_add: ["${DOCKER_SOCKET_GID:-10002}"] volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - "${UNRAID_REGISTRATIONS_FILE:-../unraid/registered-targets.example.json}:/etc/itworx/registered-targets.json:ro" networks: [hub-broker, hub-observe] connector-unraid: <<: *owned-service build: {context: ../.., dockerfile: infrastructure/images/python-service/Dockerfile, args: {SERVICE_NAME: connector-unraid}} image: local/itworx-mcp-hub-connector-unraid:dev environment: OTEL_EXPORTER_OTLP_ENDPOINT: http://observability-collector:4318 BROKER_URL: http://docker-read-broker:8080 BROKER_TOKEN_FILE: /run/secrets/docker_broker_service_token SECRET_EXPECTED_UID: "${PUID:-10001}" SECRET_EXPECTED_GID: "${PGID:-10001}" UNRAID_REGISTRATIONS_FILE: /etc/itworx/registered-targets.json CONNECTOR_IDENTITY_KEY_FILE: /run/secrets/connector_identity_signing_key REQUIRED_SECRET_FILES: /run/secrets/docker_broker_service_token,/run/secrets/connector_identity_signing_key secrets: [docker_broker_service_token, connector_identity_signing_key] volumes: - "${UNRAID_REGISTRATIONS_FILE:-../unraid/registered-targets.example.json}:/etc/itworx/registered-targets.json:ro" depends_on: {docker-read-broker: {condition: service_healthy}} networks: [hub-connectors, hub-broker, hub-observe] observability-collector: image: local/itworx-mcp-hub-observability:dev build: {context: ../.., dockerfile: infrastructure/images/observability/Dockerfile} profiles: [observability] networks: [hub-observe] healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:4318/healthz', timeout=2)"] interval: 60s timeout: 3s retries: 5 inspector: <<: *third-party image: ghcr.io/modelcontextprotocol/inspector:2.0.0-rc.1@sha256:700500bbce937754d2ac0dd95797c3e057c8ebe2f4faf80f994652b0958a7478 profiles: [inspector] networks: [hub-edge] ports: ["${INSPECTOR_BIND_IP:?Set an explicit LAN-only Inspector bind address}:${INSPECTOR_PORT:-18443}:6274"] networks: hub-edge: name: itworx-mcp-hub-edge ipam: {config: [{subnet: 203.0.113.0/24}]} hub-egress: name: itworx-mcp-hub-egress ipam: {config: [{subnet: 203.0.113.0/24}]} hub-control: name: itworx-mcp-hub-control internal: true ipam: {config: [{subnet: 203.0.113.0/24}]} hub-connectors: name: itworx-mcp-hub-connectors internal: true ipam: config: [{subnet: 203.0.113.0/24}] hub-broker: name: itworx-mcp-hub-broker internal: true ipam: {config: [{subnet: 203.0.113.0/24}]} hub-data: name: itworx-mcp-hub-data internal: true ipam: {config: [{subnet: 203.0.113.0/24}]} hub-observe: name: itworx-mcp-hub-observe internal: true ipam: {config: [{subnet: 203.0.113.0/24}]} volumes: postgres-data: {name: itworx-mcp-hub-postgres} ingestion-clamav-db: {name: itworx-mcp-hub-ingestion-clamav-db} secrets: postgres_superuser_password: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/postgres_superuser_password"} hub_database_password: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/hub_database_password"} contextforge_database_password: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/contextforge_database_password"} contextforge_auth_encryption_secret: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/contextforge_auth_encryption_secret"} contextforge_jwt_secret: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/contextforge_jwt_secret"} contextforge_admin_credential: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/contextforge_admin_credential"} contextforge_api_token: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/contextforge_api_token"} docker_broker_service_token: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/docker_broker_service_token"} connector_identity_signing_key: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/connector_identity_signing_key"} demo_token_signing_key: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/demo_token_signing_key"} authentik_oidc_client_secret: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/authentik_oidc_client_secret"} hub_session_signing_key: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/hub_session_signing_key"} gitea_mcp_token: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/gitea_mcp_token"} gitea_mcp_write_token: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/gitea_mcp_write_token"} mobilityops_service_token: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/mobilityops_service_token"} projectbrain_hub_service_token: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/projectbrain_hub_service_token"} appops_hub_service_token: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/appops_hub_service_token"} stitch_api_key: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/stitch_api_key"} knowledge_hub_ingestion_token: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/knowledge_hub_ingestion_token"} knowledge_hub_ingestion_identity_key: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/knowledge_hub_ingestion_identity_key"} redis_password: {file: "${SECRETS_DIR:?Set a secret directory outside Git}/redis_password"}