dow-prod-tracker/docker-compose.yml
DJP 7e7ef7b7c1 deploy.sh: auto-detect free host ports, render Apache conf per-deploy
You were right — everything's containerized, the host ports are just
reverse-proxy targets (+ an optional psql peephole for the db). Hardcoding
them is why the local smoke test face-planted on 5492 (amazon-transcreation
was squatting it) and would have done the same any time anything else
bound :3002 or :5492 on the shared server.

docker-compose.yml:
- ports now reference `${APP_HOST_PORT:-3002}` and `${DB_HOST_PORT:-5492}`.
  Defaults match the prior-committed values; override via env vars.
  Container-internal ports (3000, 5432) never change.

apache/dow-prod-tracker.conf → .conf.tmpl:
- Moved to a committed template with `${APP_HOST_PORT}` placeholders in
  both the WebSocket rewrite and the ProxyPass/ProxyPassReverse lines.
- deploy.sh renders the real .conf from the template on every run with
  the chosen port substituted in. Rendered .conf is gitignored so it
  can vary per server without drift.

deploy.sh:
- New is_port_free() and find_free_port() using bash's /dev/tcp — no
  external tool dependency, works identically on Ubuntu and macOS.
- After `docker compose down` (which frees any of OUR ports), probe for
  APP_HOST_PORT starting from 3002 and DB_HOST_PORT from 5492. Pick the
  first free port (scan up to 50). Warn if the preferred port was busy.
  Honors explicit override: `APP_HOST_PORT=3005 ./deploy.sh` works.
- Exports the chosen ports before `docker compose up` so compose
  substitutes them into the `ports:` mappings.
- Renders apache/dow-prod-tracker.conf from the .tmpl with the same
  APP_HOST_PORT, every deploy. If the Apache Include line is already in
  the vhost, we reload Apache anyway (picks up the re-rendered snippet
  in case the port changed).
- Health check URL uses APP_HOST_PORT.
- "Deploy complete" banner now prints the chosen ports.

.gitignore:
- Added docker-compose.override.yml (per-machine local overrides) and
  apache/dow-prod-tracker.conf (rendered by deploy.sh, varies per server).

DEPLOY.md updated with the auto-detection behaviour and override recipe.

Sanity-checked locally:
- is_port_free correctly identifies 5492 busy (amazon-transcreation),
  5493 busy (our smoke-test db), 3002 busy (Docker Desktop grabs 3000-3002
  on this Mac), and picks 5494/3003 respectively.
- `APP_HOST_PORT=3999 DB_HOST_PORT=5999 docker compose config` produces
  published ports 3999 and 5999.
- `bash -n deploy.sh` clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 19:59:30 -04:00

77 lines
3.1 KiB
YAML

name: dow-prod-tracker
services:
# ─── PostgreSQL with pgvector ───────────────────────────
db:
image: pgvector/pgvector:pg17
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
POSTGRES_DB: dow_prod_tracker
# Host port is overridable via DB_HOST_PORT env var — deploy.sh auto-picks
# a free one if 5492 is taken on the host. The container-internal port
# (5432) never changes — the app connects to db:5432 over the Docker
# network and doesn't care what host port (if any) is mapped.
ports:
- "${DB_HOST_PORT:-5492}:5432"
volumes:
- pgdata:/var/lib/postgresql/data
- ./docker/db-init.sql:/docker-entrypoint-initdb.d/01-pgvector.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
# ─── Next.js app ───────────────────────────────────────
app:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
# Host port is overridable via APP_HOST_PORT env var — deploy.sh auto-picks
# a free one if 3002 is taken, and writes the chosen port into the Apache
# reverse-proxy config (apache/dow-prod-tracker.conf) at the same time.
ports:
- "${APP_HOST_PORT:-3002}:3000"
environment:
DATABASE_URL: postgresql://postgres:${DB_PASSWORD:-postgres}@db:5432/dow_prod_tracker?schema=public
# Ollama — points to internal GPU server for embeddings + chat fallback
OLLAMA_HOST: ${OLLAMA_HOST:-http://10.24.42.219:11434}
OLLAMA_CHAT_HOST: ${OLLAMA_CHAT_HOST:-http://10.24.42.219:11434}
OLLAMA_CHAT_MODEL: ${OLLAMA_CHAT_MODEL:-gemma4:latest}
OLLAMA_EMBED_MODEL: ${OLLAMA_EMBED_MODEL:-nomic-embed-text}
NODE_ENV: production
AUTH_SECRET: ${AUTH_SECRET}
AUTH_TRUST_HOST: "true"
# Azure SPA registration — PKCE in browser, no client secret
AZURE_CLIENT_ID: ${AZURE_CLIENT_ID}
AZURE_TENANT_ID: ${AZURE_TENANT_ID}
AZURE_REDIRECT_URI: ${AZURE_REDIRECT_URI:-}
CRON_SECRET: ${CRON_SECRET:-change-me}
API_KEY: ${API_KEY:-}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-}
DEV_BYPASS_AUTH: ${DEV_BYPASS_AUTH:-false}
DEV_USER_ID: ${DEV_USER_ID:-}
# OMG webhook (Shashank pending — stub until payload confirmed)
OMG_WEBHOOK_SECRET: ${OMG_WEBHOOK_SECRET:-}
OMG_WEBHOOK_ALLOW_INSECURE: ${OMG_WEBHOOK_ALLOW_INSECURE:-false}
# Auth: Entra SSO stays coded but gated. Flip to "true" post-MVP once redirect URI is live.
NEXT_PUBLIC_AUTH_ENTRA_ENABLED: ${NEXT_PUBLIC_AUTH_ENTRA_ENABLED:-false}
volumes:
- uploads_data:/data/uploads
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:3000/api/health || exit 1"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s
volumes:
pgdata:
uploads_data: