scaffold: project skeleton, schema, healthz/readyz, CI
Initial project structure for neuronetz-gateway per scope-docs/SPEC.md: - Python 3.12 / FastAPI / SQLAlchemy 2.0 (async) / Redis / Postgres stack managed by uv. Multi-stage non-root Dockerfile, prod + dev compose files (ollama service is NEVER published in either), Caddyfile + systemd unit, justfile, GitHub Actions CI (ruff, mypy --strict, pytest, bandit, pip-audit). - Pydantic-Settings config covering every env var from SPEC §7, including the MODEL_DISCOVERY_* keys for the dynamic-discovery feature (§4.6). - Alembic 0001_initial creates the full gateway schema (8 tables, 3 enums, notify_key_revoked() trigger), incl. allow_all_models on tenant_limits and key_limits for the per-tenant auto-grant toggle. - Working /healthz, /readyz (fail-closed when deps unreachable), and a Prometheus /metrics stub. Sanitizing error handlers that attach X-Request-ID to every response and never leak upstream internals. - SPEC + AGENT_PROMPT included under scope-docs/ (source of truth).
This commit is contained in:
101
docker-compose.dev.yml
Normal file
101
docker-compose.dev.yml
Normal file
@@ -0,0 +1,101 @@
|
||||
# neuronetz-gateway — DEV stack (postgres + redis + gateway only).
|
||||
#
|
||||
# Deliberately differs from the production stack:
|
||||
# * NO caddy — the gateway is published directly on localhost:8080.
|
||||
# * NO ollama — Phase 1 expects /readyz to return 503 *because* there is no
|
||||
# Ollama backend yet. This is the intended exit-criterion state.
|
||||
#
|
||||
# Bring it up with:
|
||||
# docker compose -f docker-compose.dev.yml up --build
|
||||
#
|
||||
# Then:
|
||||
# curl -i http://localhost:8080/healthz # -> 200
|
||||
# curl -i http://localhost:8080/readyz # -> 503 (no Ollama)
|
||||
#
|
||||
# The gateway container runs `alembic upgrade head` and then starts the server.
|
||||
|
||||
services:
|
||||
gateway:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:8080:8080"
|
||||
environment:
|
||||
GATEWAY_BIND_HOST: 0.0.0.0
|
||||
GATEWAY_BIND_PORT: "8080"
|
||||
GATEWAY_LOG_LEVEL: ${GATEWAY_LOG_LEVEL:-INFO}
|
||||
GATEWAY_LOG_FORMAT: ${GATEWAY_LOG_FORMAT:-console}
|
||||
GATEWAY_REQUEST_ID_HEADER: ${GATEWAY_REQUEST_ID_HEADER:-X-Request-ID}
|
||||
GATEWAY_TRUSTED_PROXIES: ${GATEWAY_TRUSTED_PROXIES:-127.0.0.1}
|
||||
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-gateway}:${POSTGRES_PASSWORD:-gateway}@postgres:5432/${POSTGRES_DB:-neuronetz}
|
||||
DATABASE_POOL_SIZE: ${DATABASE_POOL_SIZE:-10}
|
||||
DATABASE_POOL_OVERFLOW: ${DATABASE_POOL_OVERFLOW:-20}
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
REDIS_KEY_CACHE_TTL_S: ${REDIS_KEY_CACHE_TTL_S:-60}
|
||||
# No Ollama in the dev stack — point at the (absent) service name so the
|
||||
# readiness check fails closed with 503, exactly as Phase 1 expects.
|
||||
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
|
||||
OLLAMA_CONNECT_TIMEOUT_S: ${OLLAMA_CONNECT_TIMEOUT_S:-5}
|
||||
OLLAMA_READ_TIMEOUT_S: ${OLLAMA_READ_TIMEOUT_S:-600}
|
||||
OLLAMA_MAX_CONNECTIONS: ${OLLAMA_MAX_CONNECTIONS:-64}
|
||||
DEFAULT_RPM: ${DEFAULT_RPM:-60}
|
||||
DEFAULT_TPM: ${DEFAULT_TPM:-100000}
|
||||
DEFAULT_CONCURRENT: ${DEFAULT_CONCURRENT:-8}
|
||||
MAX_REQUEST_BODY_BYTES: ${MAX_REQUEST_BODY_BYTES:-262144}
|
||||
MAX_NUM_PREDICT: ${MAX_NUM_PREDICT:-4096}
|
||||
ARGON2_TIME_COST: ${ARGON2_TIME_COST:-3}
|
||||
ARGON2_MEMORY_COST_KIB: ${ARGON2_MEMORY_COST_KIB:-65536}
|
||||
ARGON2_PARALLELISM: ${ARGON2_PARALLELISM:-4}
|
||||
AUTH_FAILURE_RATE_LIMIT_PER_IP_PER_MIN: ${AUTH_FAILURE_RATE_LIMIT_PER_IP_PER_MIN:-20}
|
||||
AUDIT_BUFFER_SIZE: ${AUDIT_BUFFER_SIZE:-1000}
|
||||
PROMPT_LOG_DEFAULT_RETENTION_DAYS: ${PROMPT_LOG_DEFAULT_RETENTION_DAYS:-30}
|
||||
AUDIT_LOG_DEFAULT_RETENTION_DAYS: ${AUDIT_LOG_DEFAULT_RETENTION_DAYS:-365}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
# Run migrations, then start the server.
|
||||
command: ["sh", "-c", "alembic upgrade head && exec python -m neuronetz_gateway"]
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8080/healthz"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-gateway}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-gateway}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-neuronetz}
|
||||
ports:
|
||||
# Exposed on localhost for dev convenience (psql, migrations from host).
|
||||
- "127.0.0.1:5432:5432"
|
||||
volumes:
|
||||
- postgres_dev_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-gateway} -d ${POSTGRES_DB:-neuronetz}"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
command: ["redis-server", "--save", "", "--appendonly", "no"]
|
||||
ports:
|
||||
# Exposed on localhost for dev convenience (redis-cli from host).
|
||||
- "127.0.0.1:6379:6379"
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
postgres_dev_data:
|
||||
Reference in New Issue
Block a user