January 30, 2026 (5mo ago)
Written by Temps Team
Last updated January 30, 2026 (5mo ago)
Deploying FastAPI to production without a dedicated DevOps team means juggling Docker, Nginx, SSL certificates, and monitoring tools that rarely work together cleanly. Temps collapses that stack into a single Rust binary you control — git-push to deploy, automatic HTTPS via Pingora (Cloudflare's open-source proxy), built-in error tracking, and request analytics, all for ~$6/mo on Temps Cloud or free to self-host.
TL;DR:
bunx @temps-sdk/cli deploy my-api -b main -e production -y— your FastAPI app is live with HTTPS and monitoring in under 5 minutes. Temps is Apache 2.0, self-hostable for free, or ~$6/mo on Temps Cloud (Hetzner cost + 30%, no per-seat fees).
The fastest path: push your code to a git repository, connect it to Temps, and run one CLI command. Temps detects Python projects, generates an optimized Dockerfile, builds the container, provisions a Let's Encrypt certificate, and starts routing traffic — all without you touching Nginx or Docker config.
After this tutorial, your FastAPI app will have:
requirements.txt or pyproject.tomlTemps works with any FastAPI project layout. Here's a minimal example:
my-fastapi-app/
├── main.py # or app/main.py
├── requirements.txt # or pyproject.toml
└── .env # optional, for local development only
main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello from FastAPI on Temps"}
@app.get("/health")
def health_check():
return {"status": "healthy"}
requirements.txt:
fastapi>=0.109.0
uvicorn[standard]>=0.27.0
# macOS / Linux
curl -fsSL https://temps.sh/install.sh | bash
bunx @temps-sdk/cli login
# Create project and connect your git repository
bunx @temps-sdk/cli projects create \
-n "My FastAPI App" \
-d "FastAPI application" \
--repo your-org/your-fastapi-app \
--branch main \
--preset python
Temps automatically detects Python projects. Use --preset docker if you already have a Dockerfile.
bunx @temps-sdk/cli deploy my-fastapi-app -b main -e production -y
Temps will:
Your app is live at your-app.temps.sh within minutes.
You don't need Docker knowledge. Temps generates an optimized Dockerfile for your Python project:
Generated Dockerfile:
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application
COPY . .
# Run with uvicorn
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
If you have a custom Dockerfile, Temps uses that instead.
Temps detects your FastAPI app through your project files. Common entry point patterns it handles:
main.py with app = FastAPI()app/main.py with app = FastAPI()src/main.py with app = FastAPI()For custom resource limits or replica counts:
# Set CPU/memory limits and replica count
bunx @temps-sdk/cli projects config -p my-fastapi-app \
--cpu-limit 1 \
--memory-limit 512 \
--replicas 2 \
-y
# Single variable
bunx @temps-sdk/cli environments vars set DATABASE_URL "postgresql://..." -e production
bunx @temps-sdk/cli environments vars set OPENAI_API_KEY "sk-..." -e production
# Import from file
bunx @temps-sdk/cli environments vars import .env.production -e production
# List all (values hidden by default)
bunx @temps-sdk/cli environments vars list -e production
import os
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
openai_api_key: str
debug: bool = False
settings = Settings()
Environment variables are encrypted at rest and injected at runtime — they never appear in your Docker image layers.
Add your database URL as an environment variable:
bunx @temps-sdk/cli environments vars set DATABASE_URL "postgresql://user:pass@host:5432/db" -e production
Use with SQLAlchemy or asyncpg:
from sqlalchemy.ext.asyncio import create_async_engine
import os
DATABASE_URL = os.getenv("DATABASE_URL")
engine = create_async_engine(DATABASE_URL)
bunx @temps-sdk/cli environments vars set REDIS_URL "redis://host:6379" -e production
import redis
import os
r = redis.from_url(os.getenv("REDIS_URL"))
bunx @temps-sdk/cli environments vars set MONGODB_URI "mongodb://user:pass@host:27017/db" -e production
from motor.motor_asyncio import AsyncIOMotorClient
import os
client = AsyncIOMotorClient(os.getenv("MONGODB_URI"))
After deployment, your Temps dashboard includes monitoring with no extra subscriptions:
No Sentry, no Datadog, no additional setup. It's built into the same binary that runs your proxy.
FastAPI's automatic documentation works out of the box on Temps:
https://your-app.temps.sh/docshttps://your-app.temps.sh/redocTo restrict access in production:
from fastapi import FastAPI
import os
app = FastAPI(
docs_url="/docs" if os.getenv("ENABLE_DOCS") else None,
redoc_url="/redoc" if os.getenv("ENABLE_DOCS") else None,
)
Temps polls your health endpoint every 5 seconds. Two consecutive failures within the 60-second error window trigger automatic rollback to the last healthy deployment. Always expose a /health route:
@app.get("/health")
async def health():
# Optionally check DB connectivity here
return {"status": "healthy"}
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://yourfrontend.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
import logging
import json
class JSONFormatter(logging.Formatter):
def format(self, record):
return json.dumps({
"timestamp": self.formatTime(record),
"level": record.levelname,
"message": record.getMessage(),
"module": record.module,
})
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logging.getLogger().addHandler(handler)
Temps aggregates container stdout/stderr logs in your dashboard, searchable and streamable via bunx @temps-sdk/cli runtime-logs -p my-fastapi-app -f.
Scale to multiple replicas:
bunx @temps-sdk/cli environments scale -e production -r 3
Temps handles load balancing automatically via Pingora, Cloudflare's open-source Rust proxy.
bunx @temps-sdk/cli projects config -p my-fastapi-app \
--cpu-limit 2 \
--memory-limit 1024 \
-y
bunx @temps-sdk/cli deploy my-fastapi-app -b main -e production -y
Add an A record pointing to your Temps server IP:
| Type | Name | Value |
|---|---|---|
| A | api | YOUR_SERVER_IP |
# HTTP-01 challenge (default, works for most domains)
bunx @temps-sdk/cli domains add -d api.yourdomain.com
# DNS-01 challenge (required for wildcard domains)
bunx @temps-sdk/cli domains add -d "*.yourdomain.com" --challenge dns-01
With --challenge dns-01, the CLI outputs a TXT record to add. Once DNS propagates, Temps completes ACME validation automatically. Let's Encrypt certificates are provisioned and renewed without manual intervention.
| Aspect | Temps | Docker + VPS manually |
|---|---|---|
| Setup time | ~5 minutes | 1–2 hours |
| Docker knowledge | Not required | Required |
| SSL setup | Automatic (Let's Encrypt) | Manual (Certbot + Nginx config) |
| Reverse proxy | Pingora (built-in) | Nginx or Caddy (manual config) |
| Error tracking | Built-in | Sentry (~$26/mo Developer plan) |
| Request analytics | Built-in | Prometheus + Grafana (manual) |
| Cost | ~$6/mo cloud or free self-host | VPS cost only, higher ops time |
| Aspect | Temps | Railway |
|---|---|---|
| Python detection | Automatic (Dockerfile generated) | Automatic (Nixpacks) |
| Pricing model | ~$6/mo flat (no per-seat fees) | See Railway pricing page |
| Error tracking | Built-in | Not included |
| Self-host option | Yes (Apache 2.0, free) | No |
| Vendor lock-in | None — runs on any Linux server | Proprietary platform |
| Health checks | Every 5s, auto-rollback in 60s | Health check support |
| Aspect | Temps | Lambda + API Gateway |
|---|---|---|
| Cold starts | None (persistent containers) | 100–500ms depending on runtime |
| Request timeout | Unlimited | 29s (API GW limit) |
| Complexity | Low — single binary | High — IAM, layers, API GW |
| Cost at scale | Predictable flat rate | Per-request variable billing |
| WebSocket/SSE | Native (Starlette) | Requires separate tooling |
from fastapi import BackgroundTasks
@app.post("/send-email")
async def send_email(
email: str,
background_tasks: BackgroundTasks
):
background_tasks.add_task(send_email_task, email)
return {"message": "Email queued"}
from fastapi import Depends
async def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users")
async def get_users(db: Session = Depends(get_db)):
return db.query(User).all()
import time
@app.middleware("http")
async def add_timing(request, call_next):
start = time.time()
response = await call_next(request)
response.headers["X-Process-Time"] = str(time.time() - start)
return response
Check build logs in the Temps dashboard or stream them via CLI:
bunx @temps-sdk/cli deploy my-fastapi-app -b main -e production
Common issues:
requirements.txtFROM python:3.11-slim in a custom Dockerfile)# Stream live logs
bunx @temps-sdk/cli runtime-logs -p my-fastapi-app -f
0.0.0.0:8000 (not 127.0.0.1)pydantic-settingsTemps checks /health every 5 seconds. If your endpoint takes more than 2 seconds to respond under normal load, add a lightweight health check that bypasses slow database queries:
@app.get("/health")
async def health():
return {"status": "ok"} # Keep it fast and dependency-free
# Install CLI
curl -fsSL https://temps.sh/install.sh | bash
# Login
bunx @temps-sdk/cli login
# Create project and connect repo
bunx @temps-sdk/cli projects create \
-n "My FastAPI API" \
-d "FastAPI application" \
--repo myorg/my-fastapi-app \
--branch main \
--preset python
# Deploy
bunx @temps-sdk/cli deploy my-fastapi-api -b main -e production -y
# Stream logs
bunx @temps-sdk/cli runtime-logs -p my-fastapi-api -f
# Set environment variable
bunx @temps-sdk/cli environments vars set SECRET_KEY "value" -e production
# Scale replicas
bunx @temps-sdk/cli environments scale -e production -r 3
# Add domain (HTTP-01 by default, DNS-01 for wildcards)
bunx @temps-sdk/cli domains add -d api.example.com
Ready to deploy your FastAPI app? Get started at temps.sh — self-host for free (Apache 2.0) or use Temps Cloud at ~$6/mo with no per-seat fees:
curl -fsSL https://temps.sh/install.sh | bash && bunx @temps-sdk/cli login