# Self-contained demo stack. Use ./up.sh (generates credentials + renders the # realm + starts everything). Brings up: Postgres + Keycloak (OIDC) + the MCP # server + Traefik. No external networks; no manual realm import or seeding. # # Credentials come from .env (generated by up.sh on first run). Requires port 80. services: # ── Database ──────────────────────────────────────────────────────────────── mcp-postgres: image: postgres:16-alpine container_name: mcp-postgres environment: POSTGRES_DB: banking_db POSTGRES_USER: mcp_user POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?run ./up.sh to generate .env} volumes: - mcp_postgres_data:/var/lib/postgresql/data - ./postgres/init:/docker-entrypoint-initdb.d:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U mcp_user -d banking_db"] interval: 5s timeout: 5s retries: 10 networks: - mcp-net # ── One-shot seeder: fills banking_db with demo data, then exits ──────────── seeder: image: ghcr.io/astral-sh/uv:python3.12-bookworm-slim container_name: mcp-seeder restart: "no" environment: DATABASE_URL: postgresql://mcp_user:${POSTGRES_PASSWORD}@mcp-postgres:5432/banking_db volumes: - ./scripts:/scripts:ro command: uv run --quiet --with faker --with asyncpg /scripts/seed.py depends_on: mcp-postgres: condition: service_healthy networks: - mcp-net # ── Identity provider (OIDC) — auto-imports the mcp-db realm on startup ───── keycloak: # 26.6+ required for Dynamic Client Registration compatibility with MCP clients image: quay.io/keycloak/keycloak:26.6 container_name: mcp-keycloak command: start-dev --import-realm environment: KC_BOOTSTRAP_ADMIN_USERNAME: admin KC_BOOTSTRAP_ADMIN_PASSWORD: admin KC_HTTP_ENABLED: "true" KC_HTTP_PORT: 8080 KC_HOSTNAME: keycloak.test KC_HOSTNAME_STRICT: "false" KC_HEALTH_ENABLED: "true" volumes: - ./keycloak/import:/opt/keycloak/data/import:ro labels: - "traefik.enable=true" - "traefik.http.routers.keycloak.rule=Host(`keycloak.test`)" - "traefik.http.routers.keycloak.entrypoints=web" - "traefik.http.services.keycloak.loadbalancer.server.port=8080" networks: - mcp-net # ── The MCP server itself ─────────────────────────────────────────────────── mcp-server: build: context: ./mcp_server dockerfile: Dockerfile container_name: mcp-server environment: DATABASE_URL: postgresql://mcp_user:${POSTGRES_PASSWORD}@mcp-postgres:5432/banking_db # One URL for Keycloak that resolves the same from the server and the browser # (Traefik carries a keycloak.test alias on the internal network — see below). KEYCLOAK_PUBLIC_URL: http://keycloak.test PUBLIC_BASE_URL: http://mcp-postgres.traefik.test KEYCLOAK_REALM: mcp-db LOG_LEVEL: INFO depends_on: mcp-postgres: condition: service_healthy keycloak: condition: service_started labels: - "traefik.enable=true" - "traefik.http.routers.mcp-postgres.rule=Host(`mcp-postgres.traefik.test`)" - "traefik.http.routers.mcp-postgres.entrypoints=web" - "traefik.http.services.mcp-postgres.loadbalancer.server.port=8000" networks: - mcp-net # ── Reverse proxy: routes keycloak.test + mcp-postgres.traefik.test ───────── traefik: image: traefik:v3.5 container_name: mcp-traefik depends_on: - dockerproxy command: - --providers.docker=true - --providers.docker.endpoint=tcp://dockerproxy:2375 - --providers.docker.exposedbydefault=false - --providers.docker.network=mcp-postgres-oidc_mcp-net # Only manage THIS project's containers, so a shared Docker host (other # Traefik stacks, other services) can't bleed into this demo's routing. - --providers.docker.constraints=Label(`com.docker.compose.project`,`mcp-postgres-oidc`) - --entrypoints.web.address=:80 - --log.level=INFO ports: # Override if 80 is taken: TRAEFIK_WEB_PORT=8080 docker compose up - "${TRAEFIK_WEB_PORT:-80}:80" networks: mcp-net: # So in-cluster callers (the MCP server fetching Keycloak metadata/JWKS) # reach these hostnames via Traefik — same URLs the browser uses. aliases: - keycloak.test - mcp-postgres.traefik.test # ── Docker-socket shim ────────────────────────────────────────────────────── # Upgrades Traefik's legacy /v1.24/ Docker API calls so they work on daemons # that require API >= 1.40 (e.g. OrbStack). See traefik/dockerproxy.conf. dockerproxy: image: nginx:alpine container_name: mcp-dockerproxy volumes: - ./traefik/dockerproxy.conf:/etc/nginx/nginx.conf:ro - /var/run/docker.sock:/var/run/docker.sock networks: - mcp-net volumes: mcp_postgres_data: networks: mcp-net: driver: bridge