Refactor/consolidate backend services - #23
Conversation
…o one backend container Collapses five separate service containers into one (backend/backend-gpu), run as five uvicorn processes via scripts/start-combined.sh, reducing the stack from 9 containers to 5. None of the five services' own code changes - they keep running as independent FastAPI apps on their existing ports, just co-located. Also fixes a pre-existing gap where the frontend called /cases through the gateway but no route existed for it - gateway/main.py now proxies /cases and /app/health to the app process, matching the existing OCR/translation/ field-mapping routes. Adds an hf_cache volume so app's embedding model survives container recreates instead of re-downloading from HuggingFace. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ollama's num_ctx is fixed at llama-server process launch, so pinging with different options than translate() (no num_ctx vs num_ctx=32768) forced a full model reload (~250s) on every ping/translate interleave. Reusing model_options for the ping keeps context size consistent and eliminates the reload thrashing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Exclude .env from the Docker build context so secrets stop getting baked into image layers via `COPY . .`. - Add restart: unless-stopped to db/ollama/backend so a crashed process (start-combined.sh brings the whole container down on any one service dying) actually gets restarted instead of staying down. - Remove the four per-service Dockerfiles (gateway, ocr, translation, field_mapping) left over from before the services were consolidated into the single root Dockerfile — nothing builds from them anymore. - Convert the root Dockerfile to a multi-stage build so gcc/libpq-dev and pip's build tooling stay in the build stage instead of shipping in the final runtime image. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Explains the cpu/gpu profile pattern, network alias tricks, port-to-process mapping on backend, and why depends_on uses required:false — including a clarification that OCR's GPU work happens in surya-inference, not backend.
There was a problem hiding this comment.
🔵 Needs a closer look
It significantly changes container/build/runtime orchestration (multi-service co-location + Compose refactor), which warrants final human validation of startup, health, and restart behavior end-to-end.
Pull request overview
This PR consolidates five backend FastAPI services (app, translation, field-mapping, OCR, gateway) into a single Docker image/container while keeping them as independent uvicorn processes on their existing ports, and updates the local Compose stack accordingly. It also closes a gateway routing gap for /cases and aligns the translation service’s Ollama health ping options with real translate calls to avoid expensive model reload thrashing.
Changes:
- Add a combined entrypoint script to launch all five services in one container and exit the container if any process dies.
- Refactor
docker-compose.yml+ rootDockerfilefor a singlebackend/backend-gpuservice (multi-stage build, consolidated dependency installs, newhf_cachevolume, restart policies). - Update gateway routing to proxy
/casesand/app/health, and fix translation Ollama ping to reuse translate-timemodel_options.
File summaries
| File | Description |
|---|---|
| lending-poc/scripts/start-combined.sh | New entrypoint that starts five uvicorn processes and exits on first failure (wait -n). |
| lending-poc/README.md | Documentation updates for the consolidated backend container and new routing behavior. |
| lending-poc/gateway/main.py | Adds /cases and /app/health proxies; includes app in aggregated health. |
| lending-poc/gateway/Dockerfile | Removes now-dead per-service Dockerfile. |
| lending-poc/field_mapping_poc/Dockerfile | Removes now-dead per-service Dockerfile. |
| lending-poc/document_processing/translation/translation_service/adapters/ollama_adapter.py | Reuses model_options in ping to prevent Ollama model reload thrash. |
| lending-poc/document_processing/translation/Dockerfile | Removes now-dead per-service Dockerfile. |
| lending-poc/document_processing/ocr/Dockerfile | Removes now-dead per-service Dockerfile. |
| lending-poc/Dockerfile | Converts to multi-stage build and installs dependencies for all five services into a shared venv. |
| lending-poc/docker-compose.yml | Collapses multiple backend services into backend/backend-gpu, adds restart policies and hf_cache. |
| lending-poc/.env.example | Adds APP_REQUEST_TIMEOUT_SECONDS example. |
| lending-poc/.dockerignore | Updates ignores for the new consolidated build context and excludes .env. |
| .gitignore | Ignores CLAUDE.md. |
Review details
- Files reviewed: 12/13 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| backend services (OCR, translation, field-mapping, and the core app/cases | ||
| API). | ||
|
|
||
| Each backend module keeps running exactly as it already does today, in its | ||
| own process/venv, on its own internal port. This gateway does not import or |
Consolidate Backend Services into a Single Container
Summary
Collapses
app,translation,field_mapping,ocr, andgatewayfrom five separate containers into one (backend/backend-gpu), taking the stack from 9 containers down to 5.None of the five services' own code changes — they keep running as independent FastAPI apps on their existing internal ports (
8000/8001/8002/8010/8080), just co-located and launched together.Also fixes a couple of build/runtime issues found while hardening this consolidated image, plus one pre-existing routing gap.
What Changed
Five Services, One Container (
e748558)scripts/start-combined.shlaunches all five as separate uvicorn processes inside one container:If any one process dies, the script brings the whole container down (
wait -n) rather than running silently degraded. This allows Docker Compose's restart policy to kick in instead of the container staying up in a half-broken state.The root
Dockerfilenow installs all five services' dependencies. Each still has its ownrequirements.txt/pyproject.toml, installed as-is — this doesn't merge their code or dependency trees, it just installs all of them into one image.docker-compose.ymldrops from 5 backend service entries to 1 (backend/backend-gpupair, same CPU/GPU opt-in pattern asollamaandsurya-inference).A new
hf_cachevolume persists the app's embedding model across container recreates instead of re-downloading it from HuggingFace every time.Fixed a Pre-existing Routing Gap
The frontend called
/casesthrough the gateway, but no route existed for it.gateway/main.pynow proxies/casesand/app/healthto the app process, matching the existing OCR/translation/field-mapping proxy routes.Ollama Ping Context-Size Mismatch (
fca388f)Ollama's
num_ctxis fixed at llama-server process launch.The translation health monitor (added in the previous branch) was pinging Ollama with different options than
translate()uses (no num_ctxvsnum_ctx=32768), which forced a full model reload (~250s) every time a ping and a translate call interleaved.The monitor now reuses the same
model_optionsfor its ping, eliminating the reload thrashing.Hardened the Consolidated Image (
c2b9ac3).envfrom the Docker build context — it was previously getting baked into image layers viaCOPY . ..restart: unless-stoppedtodb,ollama, andbackendso a crash (which now brings down the whole combined container per thewait -nbehavior above) actually restarts instead of staying downgateway,ocr,translation,field_mapping) — nothing builds from them anymore now that everything routes through the root Dockerfilegcc/libpq-devand pip's build tooling stay in the build stage instead of shipping in the final runtime imageHow to Test
cd lending-poc docker compose up --buildConfirm
docker compose psshows 5 containers instead of the previous 9:backenddbollamasurya-inferencefrontendHit the gateway's proxied routes (
/cases,/app/health, plus the existing OCR/translation/field-mapping paths) and confirm all five processes insidebackendrespond.Kill one of the five uvicorn processes inside the backend container (e.g.
docker exec+kill) and confirm the whole container exits and gets restarted by Compose, rather than continuing to run degraded.Run a translate request followed by a health-check ping (or vice versa) back-to-back and confirm there's no ~250s stall from a model reload.
Run
docker history(or inspect image size) on the built backend image to confirmgcc/libpq-devaren't present in the final layer.