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>
77 lines
3.1 KiB
YAML
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:
|