Dokploy routes the domain to port 3000, but the container only bound 8000, so the proxy had nothing to talk to and the domain returned 502 with a perfectly healthy process behind it. The frontend image already solved this by answering on both 80 and 3000 (`listen 80; listen 3000;` in nginx.conf). Do the same here rather than swap one guess for another: 3000 is what the platform routes to, and 8000 is what the README, the vite dev proxy and docker-compose all target, so binding both means the container works whichever one it is pointed at. uvicorn's CLI takes a single --port, but Server.run() accepts pre-bound sockets, so serve.py binds each port and hands the list to one uvicorn - no extra worker or second process to supervise. PORT still pins a single port for anyone who wants one; PORTS changes the pair. A port that cannot be bound is logged and skipped rather than being fatal, since losing one of the two should not take down a service the platform only routes to on the other. It exits non-zero only when nothing is listening at all, so a genuinely dead container is still reported as failed. The healthcheck moves into the same file and passes if either port answers, which keeps it from drifting out of sync with what is actually bound. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
88 lines
3.5 KiB
YAML
88 lines
3.5 KiB
YAML
# Optional: one-command local Postgres + pgvector for development.
|
|
#
|
|
# If you already have a Postgres + pgvector instance running somewhere
|
|
# (e.g. a managed/remote server you used with the original project), you
|
|
# do NOT need this - just point backend/.env at it instead.
|
|
#
|
|
# Ollama is intentionally NOT included here: install it natively on the
|
|
# host (see docs) so it can use your CPU efficiently without an extra
|
|
# container layer, and so `ollama pull` model files persist outside Docker.
|
|
#
|
|
# Usage:
|
|
# docker compose up -d # Postgres only (the default)
|
|
# docker compose --profile full up -d # Postgres + the API, with volumes
|
|
# # then in backend/.env: DB_HOST=localhost, DB_PORT=5432, DB_NAME=pgvector,
|
|
# # DB_USER=postgres, DB_PASSWORD=<the value you set below>
|
|
services:
|
|
postgres:
|
|
image: pgvector/pgvector:pg16
|
|
container_name: catalog_rag_postgres
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_DB: pgvector
|
|
POSTGRES_USER: postgres
|
|
# Override this in a local .env file next to this compose file
|
|
# (docker compose auto-loads .env) or export POSTGRES_PASSWORD
|
|
# before running `docker compose up`. Do not commit a real value.
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme}
|
|
ports:
|
|
- "5432:5432"
|
|
volumes:
|
|
- catalog_rag_pgdata:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 10
|
|
|
|
# The API. Started only with `--profile full`, so the default
|
|
# `docker compose up -d` still brings up Postgres alone as it always did.
|
|
#
|
|
# docker compose --profile full up -d --build
|
|
#
|
|
# Present mainly as the reference for the two volume mounts below - the paths
|
|
# are the same ones to configure in Dokploy.
|
|
backend:
|
|
profiles: ["full"]
|
|
build: .
|
|
container_name: catalog_rag_backend
|
|
restart: unless-stopped
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
env_file: .env
|
|
environment:
|
|
# Inside a container `localhost` is the container itself, so the DB is
|
|
# reached by service name over the compose network.
|
|
DB_HOST: postgres
|
|
DB_PORT: "5432"
|
|
DB_PASSWORD: ${POSTGRES_PASSWORD:-changeme}
|
|
# Ollama runs natively on the host, not in compose (see the note above).
|
|
OLLAMA_BASE_URL: http://host.docker.internal:11434
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
# The container listens on 3000 and 8000 at once (see serve.py), so either
|
|
# side of this mapping can change without touching the image. 8000 is
|
|
# published because vite.config.js proxies /api to 127.0.0.1:8000.
|
|
ports:
|
|
- "8000:8000"
|
|
volumes:
|
|
# WITHOUT THESE TWO MOUNTS, a redeploy silently discards:
|
|
# - every product added through the UI (POST /api/user/products/add and
|
|
# /upload-file append to data/seed_catalogs/*.json), and
|
|
# - every retrained model (the training endpoints write *.joblib).
|
|
# Both directories live inside the image, so rebuilding it resets them to
|
|
# whatever was committed to the repo.
|
|
#
|
|
# The image also carries a pristine copy at /app/.bundled, and the app
|
|
# tops up anything missing on startup without overwriting what is already
|
|
# there - so these work whether they are named volumes or bind mounts.
|
|
# See app/infrastructure/persistence.py.
|
|
- catalog_rag_data:/app/data
|
|
- catalog_rag_artifacts:/app/app/intelligence/artifacts
|
|
|
|
volumes:
|
|
catalog_rag_pgdata:
|
|
catalog_rag_data:
|
|
catalog_rag_artifacts:
|