Files
catalogue_backend/docker-compose.yml
Suriyakumarvijayanayagam 2493b86ed8 Fix nutrition upload crash, persist runtime writes, serve API on its own domain
/api/upload/nutrition called json.dumps() in a module that never imported
json, so every request to it raised NameError, was swallowed by the broad
except, and came back as "500 Database import failed". Import json.

Persist the three directories the app writes to at runtime. Products added
through the UI are appended to data/seed_catalogs/*.json and retrained models
are written to app/intelligence/artifacts/*.joblib; both live inside the image,
so a redeploy silently discarded them. The paths now come from settings
(DATA_DIR / SEED_CATALOG_DIR / MODEL_ARTIFACTS_DIR) so a volume can be mounted
on them, and catalog_engine.save_catalog resolves against DATA_DIR instead of
a working-directory-relative "data/", which landed somewhere different
depending on where the process was started from.

Mounting those volumes would otherwise have made things worse: Docker seeds a
named volume from the image on first use, but a bind mount starts empty and
just hides what the image shipped. A bind mount on /app/data would have left
the API with no seed catalogs, so the next product added would write a JSON
file containing only that product. The image now keeps pristine copies at
/app/.bundled, and restore_bundled_assets() tops up whatever a freshly mounted
directory is missing at startup without overwriting anything already there.

Configure CORS for the split-domain deployment: the React app is served from
catalogue.nearle.ai.in and calls the API on mcp.catalogue.nearle.ai.in, so the
frontend origin has to be in API_CORS_ORIGINS. A wrong list fails only in the
browser while the server logs a healthy 200, so the effective origins are now
logged at startup with a warning when they are localhost-only.

Fix FRONTEND_DIST, which looked for a sibling "frontend/" directory that is
actually named "catalogue_frontend/", so the single-port unified-serving branch
could never activate even with a build sitting next to it.

Rebuild the Dockerfile on the frontend's multi-stage pattern: dependencies
resolve into a venv in a build stage, the runtime stage copies only that.
Adds PYTHONUNBUFFERED so startup errors reach Dokploy's log pane, a liveness
HEALTHCHECK (/api/health answers 200 even when Postgres is down, so a database
blip cannot restart-loop the container), and an overridable PORT. The CMD execs
uvicorn so SIGTERM reaches it rather than the sh wrapper.

Add "from __future__ import annotations" to ollama_service and image_search,
which used PEP 604 unions in runtime-evaluated signatures and so could not be
imported below Python 3.10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 12:30:45 +05:30

85 lines
3.3 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"
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: