feat(compose): internal db network and api healthcheck
Add the internal-only legacyhub_db network (same name the teamhub federation overlay already uses) plus an edge network to the base compose. Dev keeps host-published ports via edge; the prod overlay pins data services to legacyhub_db only, closing the module-contract gap (DB/broker on internal networks). Add a curl liveness healthcheck for the api container against /api/v1/health. Verified: docker compose config for dev, prod and prod+teamhub stacks; per-service network/port/healthcheck matrix inspected via config --format json. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
10
RUNBOOK.md
10
RUNBOOK.md
@@ -27,7 +27,8 @@ cd frontend && cp .env.example .env && npm install && npm run dev
|
|||||||
## Production deploy
|
## Production deploy
|
||||||
|
|
||||||
Production overlay enables OpenSearch security plugin, removes default ports,
|
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
|
```bash
|
||||||
# 1. Ensure secrets exist
|
# 1. Ensure secrets exist
|
||||||
@@ -142,8 +143,11 @@ Two mechanisms layered together:
|
|||||||
## Network model & firewall (federation)
|
## Network model & firewall (federation)
|
||||||
|
|
||||||
- **Standalone dev** (`docker-compose.yml`): runs the full stack and publishes
|
- **Standalone dev** (`docker-compose.yml`): runs the full stack and publishes
|
||||||
ports on localhost for convenience. Direct `:8050` access bypasses the
|
ports on localhost for convenience. Data services sit on the internal-only
|
||||||
gateway and must never be exposed beyond the workstation.
|
`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
|
- **Federated / prod**: add `docker-compose.teamhub.yml`. It moves every backing
|
||||||
service onto an internal-only network (no host ports), attaches the `api` to
|
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
|
the shared `teamhub_net`, stops publishing the api port (the gateway reaches
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#
|
#
|
||||||
# This overlay narrows the dev-friendly defaults:
|
# This overlay narrows the dev-friendly defaults:
|
||||||
# - removes published ports from data services (only api stays public);
|
# - 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;
|
# - turns on the OpenSearch security plugin and forces an admin password;
|
||||||
# - requires CORS_ALLOWED_ORIGINS to be set (no localhost fallback);
|
# - requires CORS_ALLOWED_ORIGINS to be set (no localhost fallback);
|
||||||
# - bumps Java + worker concurrency for real workloads;
|
# - bumps Java + worker concurrency for real workloads;
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
|
networks: !override [legacyhub_db]
|
||||||
ports: !reset []
|
ports: !reset []
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
@@ -26,6 +28,7 @@ services:
|
|||||||
|
|
||||||
minio:
|
minio:
|
||||||
command: server /data
|
command: server /data
|
||||||
|
networks: !override [legacyhub_db]
|
||||||
ports: !reset []
|
ports: !reset []
|
||||||
environment:
|
environment:
|
||||||
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:?MINIO_ACCESS_KEY must be set}
|
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:?MINIO_ACCESS_KEY must be set}
|
||||||
@@ -33,6 +36,7 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
opensearch:
|
opensearch:
|
||||||
|
networks: !override [legacyhub_db]
|
||||||
ports: !reset []
|
ports: !reset []
|
||||||
environment:
|
environment:
|
||||||
- discovery.type=single-node
|
- discovery.type=single-node
|
||||||
@@ -44,12 +48,14 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
qdrant:
|
qdrant:
|
||||||
|
networks: !override [legacyhub_db]
|
||||||
ports: !reset []
|
ports: !reset []
|
||||||
environment:
|
environment:
|
||||||
QDRANT__SERVICE__API_KEY: ${QDRANT_API_KEY:?QDRANT_API_KEY must be set}
|
QDRANT__SERVICE__API_KEY: ${QDRANT_API_KEY:?QDRANT_API_KEY must be set}
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
|
networks: !override [legacyhub_db]
|
||||||
ports: !reset []
|
ports: !reset []
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
|
|||||||
@@ -37,10 +37,22 @@ x-common-env: &common-env
|
|||||||
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:5273,http://localhost:4173}
|
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:5273,http://localhost:4173}
|
||||||
API_KEY: ${API_KEY:-}
|
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:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
networks: [legacyhub_db, edge]
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: ${POSTGRES_DB:-legacyhub}
|
POSTGRES_DB: ${POSTGRES_DB:-legacyhub}
|
||||||
POSTGRES_USER: ${POSTGRES_USER:-legacyhub}
|
POSTGRES_USER: ${POSTGRES_USER:-legacyhub}
|
||||||
@@ -58,6 +70,7 @@ services:
|
|||||||
minio:
|
minio:
|
||||||
image: minio/minio:RELEASE.2024-08-29T01-40-52Z
|
image: minio/minio:RELEASE.2024-08-29T01-40-52Z
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
networks: [legacyhub_db, edge]
|
||||||
command: server /data --console-address ":9001"
|
command: server /data --console-address ":9001"
|
||||||
environment:
|
environment:
|
||||||
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-legacyhub}
|
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-legacyhub}
|
||||||
@@ -76,6 +89,7 @@ services:
|
|||||||
opensearch:
|
opensearch:
|
||||||
image: opensearchproject/opensearch:2.15.0
|
image: opensearchproject/opensearch:2.15.0
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
networks: [legacyhub_db, edge]
|
||||||
environment:
|
environment:
|
||||||
- discovery.type=single-node
|
- discovery.type=single-node
|
||||||
- bootstrap.memory_lock=true
|
- bootstrap.memory_lock=true
|
||||||
@@ -103,6 +117,7 @@ services:
|
|||||||
qdrant:
|
qdrant:
|
||||||
image: qdrant/qdrant:v1.11.3
|
image: qdrant/qdrant:v1.11.3
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
networks: [legacyhub_db, edge]
|
||||||
ports:
|
ports:
|
||||||
- "6333:6333"
|
- "6333:6333"
|
||||||
- "6334:6334"
|
- "6334:6334"
|
||||||
@@ -117,6 +132,7 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
networks: [legacyhub_db, edge]
|
||||||
ports:
|
ports:
|
||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -133,11 +149,20 @@ services:
|
|||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
image: legacyhub/api:latest
|
image: legacyhub/api:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
networks: [legacyhub_db, edge]
|
||||||
environment:
|
environment:
|
||||||
<<: *common-env
|
<<: *common-env
|
||||||
APP_HOST: 0.0.0.0
|
APP_HOST: 0.0.0.0
|
||||||
APP_PORT: 8000
|
APP_PORT: 8000
|
||||||
command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--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:
|
ports:
|
||||||
# Host port 8050 avoids the 8000 collision (SalesHUB / old dispatch dev).
|
# Host port 8050 avoids the 8000 collision (SalesHUB / old dispatch dev).
|
||||||
# Container still listens on 8000. Overridden by ${API_HOST_PORT}.
|
# Container still listens on 8000. Overridden by ${API_HOST_PORT}.
|
||||||
@@ -164,6 +189,7 @@ services:
|
|||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
image: legacyhub/api:latest
|
image: legacyhub/api:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
networks: [legacyhub_db, edge]
|
||||||
environment:
|
environment:
|
||||||
<<: *common-env
|
<<: *common-env
|
||||||
command: ["celery", "-A", "app.workers.celery_app", "worker", "--loglevel=INFO", "--concurrency=2"]
|
command: ["celery", "-A", "app.workers.celery_app", "worker", "--loglevel=INFO", "--concurrency=2"]
|
||||||
|
|||||||
Reference in New Issue
Block a user