camel-oasis (transitively via sentence-transformers) hard-imports torch in
oasis/social_platform/recsys.py, so torch cannot be removed while OASIS
simulation is a feature. On linux-x86_64 the default PyPI torch wheel is the
CUDA build, which dragged ~several GB of nvidia-* packages into the image
even though this server has no GPU and all LLM + embedding calls go through
an API (generate_post_vector_openai).
Fix: force torch to resolve from PyTorch's CPU-only index via [tool.uv]:
- override-dependencies torch==2.13.0, [[tool.uv.index]] pytorch-cpu, and
[tool.uv.sources] torch={index=pytorch-cpu}.
Result after re-lock: all nvidia-* + triton packages removed (0 remaining in
uv.lock), torch 2.9.1 -> 2.13.0+cpu. Verified: torch/sentence_transformers/
oasis/from app import create_app all import fine with CPU torch
(cuda: False); backend suite 201 passed. Dockerfile keeps an import-time
verify after uv sync instead of the now-unneeded nvidia uninstall step.
camel-oasis (dep of camel-ai/sentence-transformers) pulls in torch on
linux-x86_64, which drags several GB of nvidia-cuda-* / cudnn / triton
packages into the production image even though this deployment never runs an
LLM locally — all LLM calls go through an API (OpenAI-compatible) and the
server has no GPU. The nvidia-* packages are pure bloat.
After 'uv sync', uninstall all nvidia-* runtime libs + triton (torch itself
stays as a CPU runtime). Then verify the stripped env still imports psycopg,
torch, sentence-transformers and the app, so the build fails loudly if the
strip breaks anything instead of failing silently at container runtime.
Verified locally (no CUDA libs present): psycopg/torch/sentence_transformers/
create_app all import fine.
Root cause (confirmed on local): even with psycopg installed, SQLAlchemy
raises:
NoSuchModuleError: Can't load plugin: sqlalchemy.dialects:postgres
when DATABASE_URL uses the bare 'postgres://' scheme, because SQLAlchemy
only resolves 'postgresql+driver://'. The deploy's DATABASE_URL was
'postgres://...', so alembic upgrade head (run by the entrypoint before
starting services) crashed and the worker crash-looped in supervisor.
Fix:
- create_database_engine now normalizes 'postgres://' and legacy
'postgres+pq://' to 'postgresql+psycopg://' so a bare postgres scheme
works as long as psycopg is installed.
- Dockerfile build step now verifies psycopg imports after 'uv sync'
(fails the build loudly instead of a runtime crash-loop).
- Tests: 4 for URL normalization; backend suite now 201 passed.
Worker crash-loop root cause (from container log):
sqlalchemy.exc.NoSuchModuleError: Can't load plugin: sqlalchemy.dialects:postgres
Two compounding issues:
1. Dockerfile copied pyproject.toml/uv.lock, ran 'uv sync --frozen',
then 'COPY backend ./backend' which OVERWROTE those dep files with the
shipped versions. The two could differ, so every entrypoint 'uv run'
detected drift and REBUILT/re-synced the project at container runtime
(seen as repeated 'Building crowdsight-backend...' + 'Uninstalled N /
Installed 1'), never installing the psycopg Postgres driver that the
image's own lock actually lists.
2. Result: alembic upgrade head over a postgres DATABASE_URL crashed with
NoSuchModuleError -> worker crash-loop.
Fix:
- Dockerfile: COPY backend (full source) BEFORE 'uv sync --frozen --no-dev',
so the installed deps match the shipped pyproject.toml/uv.lock exactly.
- Use 'uv run --frozen' for alembic/gunicorn/worker so nothing re-syncs at
runtime.
- entrypoint: fail fast with a clear message if DATABASE_URL is postgres
but psycopg is missing (instead of a confusing alembic traceback).
Verified: entrypoint bash syntax ok; 'uv run --frozen ... import psycopg'
passes; psycopg present in git-tracked uv.lock + pyproject.
Production image had no DB migration step, so a fresh container had an
empty database: the durable worker queried the 'jobs' table before it
existed and crash-looped with sqlalchemy OperationalError 'no such table:
jobs' (supervisor restart loop).
- Add backend/docker_entrypoint.sh: fail-fast if DATABASE_URL is unset,
run 'alembic upgrade head' (idempotent), then exec supervisord.
- Dockerfile CMD now runs the entrypoint.
- supervisor: fix nodaemon typo, stream stdout/stderr to /dev/stdout +
/dev/stderr so worker errors are visible in container logs, and give
worker startsecs/startretries.
Verified locally: entrypoint bash syntax ok, alembic upgrade head
idempotent, jobs + 19 tables created, worker --once exits 0 after migrate
(previously exit 1 with no-such-table). Worker/schema tests 11 passed.