Redcorsair Railway Deployment Saga
What Happened

Railway deployments of Python apps can enter a death spiral where each “fix” creates a new problem. The redcorsair project had 30+ deployment-fix commits across 4 separate battles in July 2025.
Failure cascade observed:
- Nixpacks can’t resolve pip dependencies -> timeout
- Pin versions -> pydantic/FastAPI/MCP version conflict
- Add constraints file -> Railway can’t find it (wrong working directory)
- Generate lockfile -> works, but pywin32 breaks Linux build
- Add platform markers -> builds, but
cdfails (shell built-in, not a binary) - Wrap in
sh -c-> works, but conflicting start commands (nixpacks.toml vs railway.json vs Dockerfile) - Delete nixpacks.toml + railway.json, use only Dockerfile -> works!
- … until next deployment attempt introduces a Procfile that conflicts with Dockerfile CMD
Root Cause
Multiple deployment mechanism files (Dockerfile, nixpacks.toml, railway.json, Procfile) coexisted without a clear owner. Railway picks one unpredictably. Each new “fix” attempt added another config file to the pile rather than replacing the broken one. The lockfile was generated on macOS and included Windows-only packages. The start command used cd : a shell built-in : without a shell wrapper. Every layer of abstraction (nixpacks, constraints files, Railway-managed paths) introduced a new failure surface.
How to Avoid
- Pick ONE deployment mechanism and delete the others. Do not have Dockerfile + nixpacks.toml + railway.json + Procfile simultaneously. Railway will pick one unpredictably.
- Start with Dockerfile for Python. Railway’s Nixpacks has known issues with Python pip installation. Railway staff consistently recommends Dockerfile.
- Use
pip-compilelockfiles. Never let the build server resolve dependencies. See redcorsair-pip-lockfile-for-cloud-builds. - Use dynamic PORT. Railway assigns a random port via
$PORTenv var. Hard-coding port 8000 works locally but fails in production. - Archive, don’t delete. Move deprecated config files to a
legacy/folder rather than deleting : you’ll need them for reference when the next deployment battle starts.
Evidence
The final resolution (Jul 26, commit 4a2bbf4) archived all legacy Dockerfiles and configs to corsair_agent/legacy/, kept a single root Dockerfile, added proper .dockerignore, and used Railway’s $PORT variable. This configuration survived for the rest of the project’s life.
Timeline: Jun 30 (12 commits), Jul 1 (3 commits), Jul 3 (2 commits), Jul 8 (1 commit), Jul 9 (1 commit), Jul 17 (2 commits), Jul 21 (7 commits), Jul 22 (6 commits), Jul 26 (1 commit, final fix).
Related
- projects/redcorsair/_index : parent project
- redcorsair-pip-lockfile-for-cloud-builds
- redcorsair