diff --git a/RUNBOOK.md b/RUNBOOK.md index 5bddb43..2edf7f2 100644 --- a/RUNBOOK.md +++ b/RUNBOOK.md @@ -27,7 +27,8 @@ cd frontend && cp .env.example .env && npm install && npm run dev ## Production deploy Production overlay enables OpenSearch security plugin, removes default ports, -forces externally-supplied credentials, and disables debug routes. +pins data services to the internal-only `legacyhub_db` network (no host access, +no egress), forces externally-supplied credentials, and disables debug routes. ```bash # 1. Ensure secrets exist @@ -142,8 +143,11 @@ Two mechanisms layered together: ## Network model & firewall (federation) - **Standalone dev** (`docker-compose.yml`): runs the full stack and publishes - ports on localhost for convenience. Direct `:8050` access bypasses the - gateway and must never be exposed beyond the workstation. + ports on localhost for convenience. Data services sit on the internal-only + `legacyhub_db` network plus `edge` (so the published ports keep working); + the prod overlay drops the `edge` attachment. The `api` container carries a + docker healthcheck against `/api/v1/health`. Direct `:8050` access bypasses + the gateway and must never be exposed beyond the workstation. - **Federated / prod**: add `docker-compose.teamhub.yml`. It moves every backing service onto an internal-only network (no host ports), attaches the `api` to the shared `teamhub_net`, stops publishing the api port (the gateway reaches diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 7596565..9f32747 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -10,6 +10,7 @@ # # This overlay narrows the dev-friendly defaults: # - removes published ports from data services (only api stays public); +# - pins data services to the internal-only docker network (no egress); # - turns on the OpenSearch security plugin and forces an admin password; # - requires CORS_ALLOWED_ORIGINS to be set (no localhost fallback); # - bumps Java + worker concurrency for real workloads; @@ -17,6 +18,7 @@ services: postgres: + networks: !override [legacyhub_db] ports: !reset [] environment: POSTGRES_DB: ${POSTGRES_DB} @@ -26,6 +28,7 @@ services: minio: command: server /data + networks: !override [legacyhub_db] ports: !reset [] environment: MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:?MINIO_ACCESS_KEY must be set} @@ -33,6 +36,7 @@ services: restart: always opensearch: + networks: !override [legacyhub_db] ports: !reset [] environment: - discovery.type=single-node @@ -44,12 +48,14 @@ services: restart: always qdrant: + networks: !override [legacyhub_db] ports: !reset [] environment: QDRANT__SERVICE__API_KEY: ${QDRANT_API_KEY:?QDRANT_API_KEY must be set} restart: always redis: + networks: !override [legacyhub_db] ports: !reset [] restart: always diff --git a/docker-compose.yml b/docker-compose.yml index 79f0503..15ce268 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,10 +37,22 @@ x-common-env: &common-env CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:5273,http://localhost:4173} API_KEY: ${API_KEY:-} +# Module-contract isolation: data services live on the internal-only +# legacyhub_db network (the same name docker-compose.teamhub.yml uses, so the +# federation overlay composes without a parallel network). In dev they also +# join edge so host-published ports keep working; the prod overlay pins them +# to legacyhub_db only. api/worker need edge for the published API port and +# HuggingFace model downloads. +networks: + legacyhub_db: + internal: true + edge: {} + services: postgres: image: postgres:16-alpine restart: unless-stopped + networks: [legacyhub_db, edge] environment: POSTGRES_DB: ${POSTGRES_DB:-legacyhub} POSTGRES_USER: ${POSTGRES_USER:-legacyhub} @@ -58,6 +70,7 @@ services: minio: image: minio/minio:RELEASE.2024-08-29T01-40-52Z restart: unless-stopped + networks: [legacyhub_db, edge] command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-legacyhub} @@ -76,6 +89,7 @@ services: opensearch: image: opensearchproject/opensearch:2.15.0 restart: unless-stopped + networks: [legacyhub_db, edge] environment: - discovery.type=single-node - bootstrap.memory_lock=true @@ -103,6 +117,7 @@ services: qdrant: image: qdrant/qdrant:v1.11.3 restart: unless-stopped + networks: [legacyhub_db, edge] ports: - "6333:6333" - "6334:6334" @@ -117,6 +132,7 @@ services: redis: image: redis:7-alpine restart: unless-stopped + networks: [legacyhub_db, edge] ports: - "6379:6379" volumes: @@ -133,11 +149,20 @@ services: dockerfile: docker/Dockerfile image: legacyhub/api:latest restart: unless-stopped + networks: [legacyhub_db, edge] environment: <<: *common-env APP_HOST: 0.0.0.0 APP_PORT: 8000 command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] + healthcheck: + # /api/v1/health is liveness: it answers HTTP 200 even when a dependency + # is degraded, so the container is not restart-looped on data-store issues. + test: ["CMD", "curl", "-fsS", "http://localhost:8000/api/v1/health"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s ports: # Host port 8050 avoids the 8000 collision (SalesHUB / old dispatch dev). # Container still listens on 8000. Overridden by ${API_HOST_PORT}. @@ -164,6 +189,7 @@ services: dockerfile: docker/Dockerfile image: legacyhub/api:latest restart: unless-stopped + networks: [legacyhub_db, edge] environment: <<: *common-env command: ["celery", "-A", "app.workers.celery_app", "worker", "--loglevel=INFO", "--concurrency=2"]