This document captures key lessons learned while deploying PyQueue across two platforms:
- Backend: Railway (FastAPI + Uvicorn)
- Frontend: Netlify (Vite + React + TypeScript)
The goal was a clean, production-style deployment where the backend is treated as the source of truth and the frontend adapts accordingly.
Unlike local development (where a virtual environment may already have packages installed), Railway requires an explicit dependency manifest.
Key requirement:
requirements.txt
This file must include all runtime dependencies, for example:
fastapi
uvicorn
python-dotenv
Without this file, Railway will successfully clone the repo but fail at runtime with errors such as:
ModuleNotFoundError: No module named 'fastapi'
Lesson:
Python deployments do not implicitly infer dependencies — they must be declared.
Railway does not automatically infer how to run a FastAPI application.
A custom start command must be provided:
uvicorn main:app --host 0.0.0.0 --port $PORTImportant details:
main:apprefers tomain.pyand the FastAPI instance namedapp0.0.0.0is required for containerized environments$PORTmust be used (Railway injects this dynamically)
Lesson:
Cloud platforms expect your service to bind to the platform-provided port.
CORS origin matching in FastAPI is strict string comparison.
A subtle but critical issue encountered:
- Railway automatically appends a trailing
/to environment variables - Browsers do not include a trailing slash in the
Originheader
To prevent mismatches, the backend normalizes the origin:
FRONTEND_ORIGIN = os.getenv("FRONTEND_ORIGIN", "").rstrip("/")This ensures:
https://pyqueue.netlify.app/→https://pyqueue.netlify.app- Already-correct values remain unchanged
Lesson:
CORS operates on raw strings, not semantic URLs — normalization is essential.
During debugging, the backend logs showed:
OPTIONS /api/enqueue 400 Bad Request
This is a strong diagnostic signal.
Interpretation:
- If CORS middleware matches the request, it intercepts
OPTIONSand returns200 - A
400means the request fell through to routing logic - Therefore, the origin did not match the allowed origins
Lesson:
An
OPTIONS 400response almost always means the CORS middleware didn’t activate.
Netlify builds the frontend using Vite, which only exposes variables prefixed with VITE_.
Example:
VITE_API_BASE=https://pyqueue-production.up.railway.app
Used in code as:
const API_BASE = import.meta.env.VITE_API_BASE.replace(/\/+$/, "");The .replace() ensures consistency with backend URL handling.
For this project, the Netlify configuration was:
- Base directory: project root
- Build command:
npm run build - Publish directory:
dist - Functions directory: (not used)
Once set, Netlify handled builds and static hosting without issue.
- The backend must be explicit and defensive (dependency lists, env normalization, startup commands)
- The frontend should adapt to the backend contract, not invent one
- Small inconsistencies (like trailing slashes) can cause large failures in distributed systems
- Logging environment variables early is an effective debugging technique
- Treating deployment as part of the engineering process (not an afterthought) surfaces real-world concerns early
Deploying PyQueue reinforced an important lesson:
Production readiness is less about adding features and more about eliminating ambiguity.
By explicitly defining dependencies, normalizing inputs, and respecting platform conventions, PyQueue now runs cleanly across environments — and the same patterns will scale naturally into a future PyQueuePro deployment.