{"post":{"slug":"how-to-deploy-a-fastapi-app-2026","title":"How to deploy a FastAPI app in 2026: containers, cloud, and production config","excerpt":"Deploying FastAPI in 2026 means a minimal Docker image, a proper process manager, and the right cloud target. This guide covers every step from Dockerfile to live traffic.","category":"code","date":"April 15, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">S</span>hipping a FastAPI app to production has a clear playbook in 2026: containerize it with a lean Docker image, run Gunicorn + Uvicorn workers for process supervision, and deploy to a platform that handles TLS and scaling for you. This guide walks every step.\n\n## What changed in 2026\n\n- **Python 3.12/3.13** are the production LTS targets; the official `python:3.13-slim` base image is ~45 MB compressed.\n- **`uv`** (from Astral) is the dominant Python package manager in 2026 — installs 10–100× faster than pip, lockfile-first, drop-in replacement in Docker.\n- **Cloud Run, Fly.io, and Railway** are the go-to managed platforms for teams that don't want to manage Kubernetes. Each accepts a Dockerfile directly.\n- **Healthcheck endpoints** are table stakes — every load balancer and k8s liveness probe expects a `GET /healthz` that returns 200.\n\n## The Dockerfile (multi-stage)\n\n```dockerfile\n# Stage 1: build dependencies\nFROM python:3.13-slim AS builder\nWORKDIR /app\nCOPY pyproject.toml uv.lock ./\nRUN pip install uv && uv sync --frozen --no-dev\n\n# Stage 2: runtime image\nFROM python:3.13-slim AS runtime\nWORKDIR /app\n\n# Non-root user\nRUN adduser --disabled-password --gecos \"\" appuser\n\nCOPY --from=builder /app/.venv /app/.venv\nCOPY ./app ./app\n\nENV PATH=\"/app/.venv/bin:$PATH\"\nENV PYTHONDONTWRITEBYTECODE=1\nENV PYTHONUNBUFFERED=1\n\nUSER appuser\n\nEXPOSE 8000\nCMD [\"gunicorn\", \"app.main:app\", \\\n     \"-k\", \"uvicorn.workers.UvicornWorker\", \\\n     \"--workers\", \"4\", \\\n     \"--bind\", \"0.0.0.0:8000\", \\\n     \"--timeout\", \"120\", \\\n     \"--access-logfile\", \"-\"]\n```\n\nKeep `.dockerignore` tight:\n\n```\n__pycache__/\n*.pyc\n.env\n.env.*\ntests/\n.git/\n```\n\n## Environment configuration\n\nUse `pydantic-settings` to read from environment variables:\n\n```python\n# app/core/config.py\nfrom pydantic_settings import BaseSettings, SettingsConfigDict\n\nclass Settings(BaseSettings):\n    DATABASE_URL: str\n    SECRET_KEY: str\n    DEBUG: bool = False\n    WORKERS: int = 4\n\n    model_config = SettingsConfigDict(env_file=\".env\", extra=\"ignore\")\n\nsettings = Settings()\n```\n\nIn production, inject these via your cloud platform's secret/env UI — never bake them into the image.\n\n## Health check endpoint\n\n```python\n# Add to app/main.py\nfrom fastapi import FastAPI\nfrom sqlalchemy import text\nfrom app.db.session import AsyncSessionLocal\n\napp = FastAPI()\n\n@app.get(\"/healthz\", include_in_schema=False)\nasync def health():\n    try:\n        async with AsyncSessionLocal() as db:\n            await db.execute(text(\"SELECT 1\"))\n        return {\"status\": \"ok\"}\n    except Exception:\n        from fastapi import Response\n        return Response(status_code=503, content=\"db unavailable\")\n```\n\n## Deployment targets comparison\n\n| Platform | Best for | Cold starts | Managed DB |\n| --- | --- | --- | --- |\n| Cloud Run (GCP) | Serverless, pay-per-request | ~1–2 s | Cloud SQL |\n| Fly.io | Always-on, global PoPs | Near zero | Fly Postgres |\n| Railway | Simplest DX, hobby/startup | Near zero | Railway Postgres |\n| ECS Fargate | AWS-native, no k8s overhead | ~10 s | RDS |\n| Kubernetes | Full control, team expertise | Depends on HPA | Any |\n\nFor most SaaS apps: **Railway** to ship fast, **Cloud Run** or **Fly.io** as you scale.\n\n## How to pick your deployment target\n\n1. **Solo project or early startup** — Railway or Fly.io. One `railway up` or `fly deploy`.\n2. **GCP-native stack** — Cloud Run. Scales to zero, integrates with Cloud SQL and Secret Manager.\n3. **AWS-native** — ECS Fargate + ECR + RDS. More IAM to configure, but fits existing AWS accounts.\n4. **Full control** — Kubernetes (GKE, EKS, AKS). Overkill until you need it.\n\n## Deploying to Fly.io (fastest path)\n\n```bash\n# One-time setup\nfly launch --dockerfile Dockerfile --no-deploy\nfly secrets set DATABASE_URL=\"postgresql+asyncpg://...\" SECRET_KEY=\"...\"\n\n# Deploy\nfly deploy\n\n# Scale workers (optional)\nfly scale count 2\n```\n\n## Common mistakes\n\n**Running with `uvicorn app.main:app --reload` in production.** The `--reload` flag watches the filesystem and restarts constantly; it is a dev-only flag.\n\n**No health check.** Load balancers send traffic to broken instances. A `/healthz` that checks the DB connection catches the most common failure mode.\n\n**Too many workers.** Each Gunicorn worker is a full Python process. 4–8 workers on a 2-core instance exhausts RAM. Start at `(2 × cores) + 1` and measure.\n\n**Not setting `--timeout`** on Gunicorn. Without it, a slow DB query hangs the worker until the OS kills it; set a realistic timeout (30–120 s).\n\n**Exposing debug mode.** `DEBUG=True` in production can leak stack traces. Gate it on an env var and default to False.\n\n## What to skip\n\n- **Running the app as root in the container** — unnecessary privilege; add a non-root user.\n- **Alpine-based Python images** — glibc compatibility issues with many Python packages make `slim` images the safer choice.\n- **Storing logs in the container** — write to stdout/stderr and let the platform collect them.\n\n## FAQ\n\n**How many Uvicorn workers should I use?**\nStart with `(2 × CPU cores) + 1`. For I/O-heavy apps (DB, HTTP calls), workers can be higher; for CPU-heavy (ML inference), match cores exactly.\n\n**Do I need Nginx in front of FastAPI?**\nOn managed platforms (Cloud Run, Fly, Railway), no — they handle TLS termination and load balancing. On bare VMs, yes: Nginx handles TLS, static files, and buffering.\n\n**How do I handle database migrations on deploy?**\nRun `alembic upgrade head` as an init container or entrypoint script before the app starts. Do not run migrations from the Gunicorn CMD.\n\n**Is Docker Compose okay for production?**\nFor a single-server deployment with low traffic, it works. For anything that needs scaling or zero-downtime deploys, use a proper platform.\n\n## Where to go next\n\nSee [How to build an API in Python in 2026](/blog/how-to-build-an-api-in-python-2026), [How to write a database migration in 2026](/blog/how-to-write-a-database-migration-2026), and [How to set up Postgres locally in 2026](/blog/how-to-set-up-postgres-locally-2026).","hue":320,"metaTitle":"How to Deploy a FastAPI App 2026: Docker, Gunicorn, Cloud","metaDescription":"Step-by-step guide to deploying FastAPI in 2026 with Docker, Gunicorn, and Uvicorn workers. Covers multi-stage builds, env config, health checks","keywords":["how to deploy fastapi app 2026","fastapi docker deployment","fastapi production gunicorn uvicorn","fastapi kubernetes deploy","fastapi docker multi-stage build","fastapi environment variables","fastapi health check","deploy python api 2026","fastapi cloud run deployment","fastapi fly.io deployment"],"tldr":["**Use a multi-stage Docker build** — a ~100 MB final image beats a 1 GB default Python image; strip dev dependencies and use a non-root user.","**Run Uvicorn via Gunicorn in production** — `gunicorn -k uvicorn.workers.UvicornWorker` gives you multi-core utilization with proper process supervision.","**Set workers = (2 × CPU cores) + 1** as a starting point; profile under load before fixing a number in production.","**Never bake secrets into the image** — inject via environment variables or a secrets manager at runtime; use `.dockerignore` to exclude `.env` files.","**Skip:** running a bare `uvicorn` command in production without a process manager. One unhandled exception kills the process and traffic hangs."],"readTime":"4 min read","wordCount":884},"related":[{"slug":"how-to-deploy-a-web-app-2026","title":"How to deploy a web app in 2026: from local to live in the right way","excerpt":"Deploying a web app in 2026 is faster than ever, but the choices are overwhelming. Here is the clear path from working locally to running reliably in production.","category":"code","date":"April 15, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":321},{"slug":"how-to-deploy-a-react-app-2026","title":"How to Deploy a React App in 2026: a step-by-step guide","excerpt":"The fastest way to deploy a React app in 2026 is to build it and push it to a static host that runs the build for you. Here are the exact steps and common pitfalls.","category":"code","date":"May 6, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":212},{"slug":"how-to-containerize-an-app-2026","title":"How to containerize an app in 2026: Docker, layers, and production-ready images","excerpt":"Containerizing an app is now a baseline skill for every backend developer. This guide covers Dockerfiles, multi-stage builds, and production best practices.","category":"code","date":"April 14, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":310}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"how-to-delegate-tasks-2026","title":"How to delegate tasks in 2026: give work away without losing quality","excerpt":"Delegation is the leverage point most managers never fully use. Here is how to hand off work clearly in 2026 so things get done well without constant follow-up.","category":"guides","date":"April 15, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":280},"older":{"slug":"how-to-deploy-a-static-site-2026","title":"How to deploy a static site in 2026","excerpt":"Static site deployment is fast and cheap in 2026, but the choices between platforms, CDN strategies, and build pipelines still matter. Here is the clear path.","category":"code","date":"April 15, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":308}}