Railway’s FastAPI deployment is a Dockerfile (or Nixpacks auto-detect), a uvicorn start command, environment variables in the dashboard, and a Postgres add-on. The right answer is the auto-detect for a simple service (the build figures out the dependencies), the Dockerfile for a custom build (a specific Python version, a multi-stage build, a non-standard system library), the right answer for the worker is a separate Railway service (the worker is a different process with a different start command).
Table of contents
- The auto-detect — the right answer for a simple FastAPI service
- The Dockerfile — the right answer for a custom build
- The environment variables — the right way to set them
- The Postgres add-on — the right way to add a database
- The worker — the right answer is a separate service
- The one mistake that breaks the worker
- The health check — the right way to make Railway happy
- How this fits the rest of the stack
- FAQ
The auto-detect — the right answer for a simple FastAPI service
Railway’s Nixpacks auto-detects the runtime (Python, Node, Go, etc.), the build runs pip install -r requirements.txt, the start command is set in the Procfile or the dashboard. The right answer is the auto-detect for a simple FastAPI service (a single-file app, a standard requirements.txt, no native dependencies). The wrong answer is the auto-detect for a custom build — the team needs the Dockerfile.
The Dockerfile — the right answer for a custom build
The right answer for a custom build is the Dockerfile. The team writes a multi-stage Dockerfile (the build stage installs the dev dependencies, the runtime stage installs the prod dependencies), the Dockerfile is in the repo, Railway’s CLI detects the Dockerfile and uses it for the build.
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
The right answer for the start command is uvicorn app:app --host 0.0.0.0 --port 8000. The --host 0.0.0.0 is critical (the app binds to all interfaces, the Railway proxy can reach the app), the --port 8000 matches the Dockerfile’s EXPOSE.
The environment variables — the right way to set them
The environment variables are set in the Railway dashboard (Variables tab), or via the CLI (railway variables set KEY=value). The right answer is the dashboard for the team’s first setup, the CLI for the team’s CI. The right answer for a secret is the platform’s secret store, not the dashboard’s Variables tab.
The Postgres add-on — the right way to add a database
The right answer for a Postgres database is the Railway Postgres add-on. The team adds the service to the project, Railway provisions a Postgres, the team gets a DATABASE_URL variable on the add-on. The team’s FastAPI service reads os.environ['DATABASE_URL'], the team’s app connects to the Postgres.
The worker — the right answer is a separate service
The right answer for a worker (a background job, a scheduled task) is a separate Railway service. The worker is a different process with a different start command (python worker.py instead of uvicorn app:app), the worker has its own resources, the worker can be scaled independently of the web service.
The wrong answer is to run the worker in the same process as the web service (the worker blocks the event loop, the web service is unresponsive). The right answer is the separate service, the right answer for the schedule is the cron add-on or the in-process scheduler.
The one mistake that breaks the worker
The mistake: the team’s worker is in the same process as the web service (a BackgroundTasks in FastAPI). The worker is a long-running task, the worker blocks the event loop, the web service is unresponsive. The right answer is a separate Railway service for the worker, the wrong answer is BackgroundTasks for a production worker.
The health check — the right way to make Railway happy
Railway’s health check is a GET request to a path on the service. The right answer is a /health endpoint that returns 200 with {"status": "ok"}, the right answer for a deep health check is a check of the database connection. The wrong answer is a 200 with no body — the team is paged on a database outage, the health check returns 200, the team is confused.
How this fits the rest of the stack
The infrastructure question is a small piece of a larger pattern: the team’s runtime, storage, database, secret store, logs, and deployment platform are all parts of the same platform. The right answer is to model the full stack before the project ships, not after. The RunxBuild hosting calculator is the right place to do that exercise — pick the runtime, the memory tier, the storage, the secret store, and the egress, and the calculator shows what the deploy actually costs at the team’s actual usage.
Useful related references:
FAQ
How do I deploy FastAPI to Railway?
railway init for a new project, railway up to deploy, the auto-detect figures out the Python build, the uvicorn start command is set in the Procfile or the dashboard.
Do I need a Dockerfile for FastAPI on Railway?
No — the auto-detect works for a simple FastAPI service. The right answer is a Dockerfile for a custom build (a specific Python version, a multi-stage build, a non-standard system library).
What is the right uvicorn command for Railway?
uvicorn app:app --host 0.0.0.0 --port 8000. The --host 0.0.0.0 is critical — the app binds to all interfaces, the Railway proxy can reach the app.
How do I add a Postgres database to Railway?
Add the Postgres add-on to the project. The team gets a DATABASE_URL variable, the FastAPI service reads it via os.environ.
Should I run a worker in the same process as the web service?
No — the worker blocks the event loop. The right answer is a separate Railway service for the worker, with its own start command and its own resources.
How do I set environment variables in Railway?
In the dashboard’s Variables tab, or via the CLI. The right answer for a secret is the platform’s secret store, not the Variables tab.
How do I add a health check to a FastAPI service on Railway?
Add a /health endpoint that returns 200 with status ok. The right answer for a deep health check is to check the database connection.
What is the right Python version for FastAPI on Railway?
The version the team’s code supports. The right answer is to pin the version in runtime.txt or the Dockerfile.