deploy: target jwilder-proxy production stack
Production deployment now matches the host setup that already runs neuronetz.ai / neuro-landing: the gateway sits behind the jwilder nginx-proxy + acme-companion already on the host, instead of bundling its own Caddy sidecar. - docker-compose.yml: drop the Caddy service entirely. The gateway joins an external `proxy` Docker network (the same one neuronetz-web / neuronetz-www use) and advertises itself with VIRTUAL_HOST / VIRTUAL_PORT / LETSENCRYPT_HOST / LETSENCRYPT_EMAIL. nginx-proxy routes TLS-terminated traffic to it on the shared network; acme-companion handles Let's Encrypt issuance + renewal for api.neuronetz.ai automatically. NO host ports are published in this compose file anywhere — gateway, postgres, redis, ollama all stay unreachable from the host. Pinned container_names (neuronetz-gateway / -postgres / -redis / -ollama) for stable identification by nginx-proxy and ops scripts. - .env.example: add GATEWAY_VIRTUAL_HOST + LETSENCRYPT_EMAIL; flip the default GATEWAY_TRUSTED_PROXIES to `127.0.0.1,nginx-proxy`. - docs/DEPLOYMENT.md: the canonical path is now jwilder-proxy. Reorganized prerequisites + steps around it; documented adding HSTS and the other security headers via the nginx-proxy custom-config mechanism (/etc/nginx/vhost.d/<host>). The Caddy sidecar lives on as a documented alternative for hosts without jwilder-proxy (ops/caddy/Caddyfile.example is kept). The Ollama-never-exposed non-negotiable is unchanged.
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
# neuronetz-gateway — FULL production stack (SPEC §4.1).
|
||||
# neuronetz-gateway — FULL production stack, hosted behind jwilder-proxy
|
||||
# (the same setup used by neuronetz.ai / neuro-landing).
|
||||
#
|
||||
# Internet ──TLS──▶ caddy ──HTTP/1.1 internal──▶ gateway ──▶ postgres / redis / ollama
|
||||
# Internet ──TLS──▶ nginx-proxy (jwilder) ──HTTP/1.1──▶ gateway
|
||||
# │
|
||||
# └─▶ postgres / redis / ollama
|
||||
# (private network only)
|
||||
#
|
||||
# Only Caddy publishes ports to the host. The gateway is reachable solely through
|
||||
# Caddy on the internal network. Postgres, Redis and (critically) Ollama are NOT
|
||||
# published to the host at all.
|
||||
# Public traffic is terminated by the jwilder/nginx-proxy that already runs on
|
||||
# this host. The gateway joins its external `proxy` network and advertises
|
||||
# itself with VIRTUAL_HOST / VIRTUAL_PORT; letsencrypt-nginx-proxy-companion
|
||||
# obtains and renews the cert for api.neuronetz.ai automatically.
|
||||
#
|
||||
# ┌─────────────────────────────────────────────────────────────────────────┐
|
||||
# │ SECURITY NON-NEGOTIABLE: │
|
||||
@@ -14,45 +19,44 @@
|
||||
# │ unauthenticated exposure this whole project exists to close. │
|
||||
# └─────────────────────────────────────────────────────────────────────────┘
|
||||
#
|
||||
# Copy `.env.example` to `.env` and adjust before running:
|
||||
# Prerequisites on the host:
|
||||
# - A jwilder-proxy stack (nginx-proxy + acme-companion) already running and
|
||||
# attached to an external Docker network named `proxy`.
|
||||
# - DNS A/AAAA record for api.neuronetz.ai pointing at this host.
|
||||
#
|
||||
# Bring it up:
|
||||
# cp .env.example .env # set POSTGRES_PASSWORD and any overrides
|
||||
# docker compose up -d --build
|
||||
#
|
||||
# Users without jwilder-proxy can use the Caddy sidecar example in
|
||||
# ops/caddy/Caddyfile.example instead — see docs/DEPLOYMENT.md.
|
||||
|
||||
services:
|
||||
caddy:
|
||||
image: caddy:2-alpine
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
gateway:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp" # HTTP/3
|
||||
volumes:
|
||||
- ./ops/caddy/Caddyfile.example:/etc/caddy/Caddyfile:ro
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
networks:
|
||||
- edge
|
||||
- internal
|
||||
|
||||
gateway:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: neuronetz-gateway
|
||||
restart: unless-stopped
|
||||
# NOTE: deliberately NO `ports:` — the gateway is internal-only and is
|
||||
# reached exclusively through Caddy.
|
||||
# NOTE: deliberately NO `ports:` — the gateway is reached only via the
|
||||
# jwilder nginx-proxy on the shared external `proxy` network.
|
||||
expose:
|
||||
- "8080"
|
||||
environment:
|
||||
# jwilder/nginx-proxy + acme-companion routing (matches neuro-landing).
|
||||
VIRTUAL_HOST: ${GATEWAY_VIRTUAL_HOST:-api.neuronetz.ai}
|
||||
VIRTUAL_PORT: "8080"
|
||||
LETSENCRYPT_HOST: ${GATEWAY_VIRTUAL_HOST:-api.neuronetz.ai}
|
||||
LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL:-admin@neuronetz.ai}
|
||||
# ─── Gateway runtime ──────────────────────────────────────────────
|
||||
GATEWAY_BIND_HOST: 0.0.0.0
|
||||
GATEWAY_BIND_PORT: "8080"
|
||||
GATEWAY_LOG_LEVEL: ${GATEWAY_LOG_LEVEL:-INFO}
|
||||
GATEWAY_LOG_FORMAT: ${GATEWAY_LOG_FORMAT:-json}
|
||||
GATEWAY_REQUEST_ID_HEADER: ${GATEWAY_REQUEST_ID_HEADER:-X-Request-ID}
|
||||
GATEWAY_TRUSTED_PROXIES: ${GATEWAY_TRUSTED_PROXIES:-127.0.0.1,caddy}
|
||||
# Service-name addressing on the internal network.
|
||||
# nginx-proxy forwards from the `proxy` network — trust its IP space.
|
||||
GATEWAY_TRUSTED_PROXIES: ${GATEWAY_TRUSTED_PROXIES:-127.0.0.1,nginx-proxy}
|
||||
# ─── Internal service addressing ──────────────────────────────────
|
||||
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-gateway}:${POSTGRES_PASSWORD:-changeme}@postgres:5432/${POSTGRES_DB:-neuronetz}
|
||||
DATABASE_POOL_SIZE: ${DATABASE_POOL_SIZE:-10}
|
||||
DATABASE_POOL_OVERFLOW: ${DATABASE_POOL_OVERFLOW:-20}
|
||||
@@ -62,6 +66,8 @@ services:
|
||||
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}
|
||||
MODEL_DISCOVERY_REFRESH_S: ${MODEL_DISCOVERY_REFRESH_S:-60}
|
||||
MODEL_DISCOVERY_CACHE_TTL_S: ${MODEL_DISCOVERY_CACHE_TTL_S:-120}
|
||||
DEFAULT_RPM: ${DEFAULT_RPM:-60}
|
||||
DEFAULT_TPM: ${DEFAULT_TPM:-100000}
|
||||
DEFAULT_CONCURRENT: ${DEFAULT_CONCURRENT:-8}
|
||||
@@ -74,6 +80,9 @@ services:
|
||||
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}
|
||||
# Playground + auto-docs OFF by default in prod.
|
||||
PLAYGROUND_ENABLED: ${PLAYGROUND_ENABLED:-false}
|
||||
DOCS_ENABLED: ${DOCS_ENABLED:-false}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
@@ -90,10 +99,12 @@ services:
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- internal
|
||||
- proxy # for nginx-proxy / acme-companion (TLS-fronted public traffic)
|
||||
- internal # for postgres / redis / ollama (private)
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: neuronetz-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-gateway}
|
||||
@@ -112,6 +123,7 @@ services:
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: neuronetz-redis
|
||||
restart: unless-stopped
|
||||
command: ["redis-server", "--save", "", "--appendonly", "no"]
|
||||
# No `ports:` — Redis is internal-only.
|
||||
@@ -129,6 +141,7 @@ services:
|
||||
# ───────────────────────────────────────────────────────────────────────────
|
||||
ollama:
|
||||
image: ollama/ollama:latest
|
||||
container_name: neuronetz-ollama
|
||||
restart: unless-stopped
|
||||
# !!! NO `ports:` — never publish Ollama to the host or the internet. !!!
|
||||
volumes:
|
||||
@@ -137,16 +150,14 @@ services:
|
||||
- internal
|
||||
|
||||
networks:
|
||||
# Public-facing network: only Caddy is attached alongside `internal`.
|
||||
edge:
|
||||
driver: bridge
|
||||
# External network managed by the host's jwilder-proxy stack
|
||||
# (the same network neuronetz-web / neuronetz-www are attached to).
|
||||
proxy:
|
||||
external: true
|
||||
# Private network for inter-service traffic; not reachable from the host.
|
||||
internal:
|
||||
driver: bridge
|
||||
internal: false
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
ollama_data:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
|
||||
Reference in New Issue
Block a user