Pitfall Preferences redcorsair

Railway Nixpacks Python Dependency Hell

railwaynixpackspythonpip

Railway + nixpacks + Complex Python Dependencies = 6 Hours of Pain

What Happened

Deploying a FastAPI + MCP SDK Python server to Railway. What should have been a single deploy command turned into 12 consecutive fix commits across 6 hours (Jun 30, 14:27 - 21:05).

The Cascade

Each “fix” revealed the next problem:

  1. nixpacks didn’t detect Python -> added providers = ["python"] to nixpacks.toml
  2. externally-managed-environment -> pip refuses to install into system Python on newer base images; needed virtualenv
  3. anyio version conflict -> FastAPI wanted anyio<4, MCP SDK wanted anyio>=4.5; upgraded FastAPI to 0.115+
  4. pydantic vs pydantic-settings -> MCP requires pydantic-settings>=2.5.2 but pinned pydantic<2.10 was incompatible
  5. PyYAML build from source failed -> Railway’s build image lacked C compiler for PyYAML’s Cython extension; pinned binary wheel version
  6. pip resolver timeout -> with all the loose constraints, pip explored hundreds of candidates; added constraints file
  7. Constraints file not found -> nixpacks changed working directory during install; fixed path references
  8. Still timing out -> generated full lockfile (requirements.in -> requirements.txt with 905 pinned lines)
  9. pywin32 in lockfile -> generated on macOS, included Windows-only package; removed manually
  10. Start command crash -> Railway exec’d the command directly; needed shell wrapper
  11. Import errors -> wrong class names in mcp_http_server.py (RedCorsairMCPServer vs actual class)
  12. Non-existent method call -> get_request_count_total didn’t exist on the rate limiter

Root Cause

See definitions/root-cause-analysis for the analytical framework. Specific cause: nixpacks abstracts away the build environment but provides no escape hatch when pip resolution goes wrong. With a complex dependency tree (FastAPI + MCP SDK + Weaviate client + Google AI + Pydantic), the resolver had too many degrees of freedom.

How to Avoid

Delete nixpacks.toml and railway.json entirely. Write a Dockerfile with explicit control over Python version, pip install order, and working directory. Build time dropped from “timeout” to ~30 seconds.

nixpacks is fine for flask + gunicorn. For anything with >20 dependencies and version conflicts, start with a Dockerfile. The time “saved” by nixpacks auto-detection is negative when you spend 6 hours debugging its abstractions. See redcorsair-railway-dockerfile-over-nixpacks for the pattern.