SeanMiller
Backend Development

Deploying a Backend: A Guide to FastAPI and Fly.io

Sean Miller
#python#fastapi#fly.io#docker#deployment

Abstract geometric background

Disclaimer: This post is a personal, educational reference. All code examples and opinions are my own and are not affiliated with, sponsored by, or representative of my employer. I’m publishing this on my own time and without using any confidential information.

© 2026 Sean Miller. All rights reserved.


In modern app development, it’s common to start with a frontend-driven workflow (React, Vue, Svelte, etc.). It lets you iterate quickly: refine UX, pressure test flows, and nail down data schemas with mock data. But prototypes have a way of turning into products. Under pressure, you get piecemeal integrations (Auth, DB, payments), and before long the frontend is carrying far too much responsibility.

At some point you need a backend: a real server-side home for business logic, data access, and secrets.

The goal: build a FastAPI service, containerize it with Docker, and deploy it to Fly.io.

What You’ll Build

If you already have a backend and you just want the Fly deployment steps, skip down to Steps.

What is a Backend?

Start with a common scenario: an enterprise with three applications and no shared backend.

--- title: Simple Enterprise Scenario without a Backend --- flowchart LR subgraph teams["Teams"] T1["Web Team"] T2["Productivity Team"] T3["Android Team"] T4["iOS Team"] end subgraph applications["Applications"] A1["User-Facing Web App"] A2["Internal Admin Dashboard"] A3["Mobile Apps"] end subgraph requirements["Requirements"] R1["User Authentication"] R2["User Authorization"] R3["Data Storage"] R4["Business Logic"] end subgraph data_stores["Data Stores"] DS1["Users DB"] DS2["Products DB"] DS3["Orders DB"] end T1 --> A1 T2 --> A2 T3 --> A3 T4 --> A3 A1 --> R1 A1 --> R2 A1 --> R3 A1 --> R4 A2 --> R1 A2 --> R2 A2 --> R3 A2 --> R4 A3 --> R1 A3 --> R2 A3 --> R3 A3 --> R4

In this scenario, we have four teams that manage three separate applications: a user-facing web app, a user-facing mobile app, and an internal admin dashboard. Each team maintains its own business logic and integrations directly with the databases. This leads to duplicated effort, inconsistent authorization rules, and a security posture that’s hard to reason about.

The same enterprise with a shared backend looks different.

--- title: Same Scenario with a Shared Backend --- flowchart LR subgraph teams["Teams"] T1["Web Team"] T2["Productivity Team"] T3["Android Team"] T4["iOS Team"] end subgraph applications["Applications"] A1["User-Facing Web App"] A2["Internal Admin Dashboard"] A3["Mobile Apps"] end Backend["Backend API"] subgraph requirements["Requirements"] R1["User Authentication"] R2["User Authorization"] R3["Data Storage"] R4["Business Logic"] end subgraph data_stores["Data Stores"] DS1["Users DB"] DS2["Products DB"] DS3["Orders DB"] end T1 --> A1 T2 --> A2 T3 --> A3 T4 --> A3 A1 --> Backend A2 --> Backend A3 --> Backend Backend --> R1 Backend --> R2 Backend --> R3 Backend --> R4 Backend --> DS1 Backend --> DS2 Backend --> DS3

A backend is the server-side integration layer of an application. It’s the part that runs on a server and handles business logic, data access, and AuthN/AuthZ. It’s the layer your frontend talks to when it needs something durable and authorized to happen.

In practice, “backend” usually means:

The Case for a Dedicated Backend

Platforms like Supabase and Firebase can handle a lot before you need a traditional backend. But custom business logic, side effects, and strict control over secrets all demand a dedicated server-side layer.

If you’re brand new to backend work, this is the mindset shift: your backend is the contract between your UI and your data. Treat it as a product in its own right.

What is FastAPI?

FastAPI is a modern, high-performance web framework for building APIs with Python. It’s built on top of Starlette (ASGI) and Pydantic (validation + serialization).

FastAPI’s killer feature is that you can write Python functions with type hints and get:

without writing a bunch of glue code.

What is Fly.io?

Fly.io is a platform for deploying and running your apps as containers on lightweight VMs (“machines”), close to your users. It’s a strong fit for small-to-medium APIs because the workflow is simple:

  1. Build a Docker image.
  2. Deploy it.
  3. Get a public URL, logs, and scaling controls.

Fly runs real processes in containers. It is not a serverless function platform. The two most common deployment bugs are binding to 127.0.0.1 instead of 0.0.0.0, and listening on the wrong port.

Alternatives

Several alternatives exist: serverless functions (AWS Lambda), serverless containers (Cloud Run), and edge runtimes (Cloudflare Workers). The trade space at a glance:

PlatformTypeProsCons
AWS LambdaServerless FunctionScales to zero; huge ecosystem; fine-grained IAMCold starts; packaging Python deps can be annoying; local dev can be awkward
Google Cloud RunServerless Container”Just a container”; scales well; straightforward HTTP servicesMore cloud surface area (GCP); IAM/networking complexity if you’re new
Supabase Edge FunctionsEdge FunctionTight Supabase integration; low ops overheadEdge runtime constraints; not ideal for heavy Python backends
Vercel Edge FunctionsEdge FunctionGreat if you live in Vercel; very fast at the edgeNot a general-purpose backend; runtime limits; best for light logic
Cloudflare WorkersEdge FunctionExtremely fast global edge; generous free tierDifferent runtime model; not a drop-in for typical Python APIs

Steps

The example creates two endpoints: GET /health and POST /simulate (deterministic calculations based on input parameters).

0) Prerequisites

The examples below use the fly CLI. Some setups still use flyctl. Same tool.

1) Create the project

mkdir fastapi-fly-example # create new directory
cd fastapi-fly-example # change to directory
python3 -m venv .venv # create virtual environment
source .venv/bin/activate # activate virtual environment
python -m pip install --upgrade pip

Install dependencies:

pip install fastapi "uvicorn[standard]"

Create a minimal main.py:

from __future__ import annotations

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI(title="FastAPI on Fly.io", version="0.1.0")


@app.get("/health")
def health():
    return {"status": "ok"}


class SimulateRequest(BaseModel):
    seed: int = Field(..., description="Starting seed for a deterministic simulation")
    steps: int = Field(10, ge=1, le=100_000)


@app.post("/simulate")
def simulate(body: SimulateRequest):
    # Simple deterministic PRNG (linear congruential generator)
    value = body.seed & 0x7FFFFFFF
    for _ in range(body.steps):
        value = (1103515245 * value + 12345) & 0x7FFFFFFF

    return {"seed": body.seed, "steps": body.steps, "result": value}

Create a requirements.txt (pin versions later if you want reproducibility):

fastapi
uvicorn[standard]

2) Run locally

Start the server:

uvicorn main:app --reload --host 127.0.0.1 --port 8000

Test it:

curl -s http://127.0.0.1:8000/health
curl -s -X POST http://127.0.0.1:8000/simulate \
  -H "Content-Type: application/json" \
  -d '{"seed":42,"steps":1000}'

FastAPI also gives you API docs out of the box:

3) Dockerize the API

Create a Dockerfile:

FROM python:3.12-slim

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]

Create a .dockerignore:

.venv
__pycache__
*.pyc
.pytest_cache
.mypy_cache
.ruff_cache
.DS_Store

Build + run the container:

docker build -t fastapi-fly-example .
docker run --rm -p 8080:8080 fastapi-fly-example

In another terminal:

curl -s http://127.0.0.1:8080/health

4) Create the Fly app

Authenticate:

fly auth login

Initialize the app (this creates fly.toml):

fly launch --no-deploy

Open fly.toml and confirm the internal port matches your Docker command above (8080). Depending on your CLI version, this may be under [http_service] or [[services]].

# fly.toml (relevant part)
[http_service]
  internal_port = 8080
  force_https = true

5) Deploy

fly deploy

Then open the app:

fly open

6) Verify in production

Grab your app URL (from fly open or fly status) and test:

curl -s https://YOUR-APP.fly.dev/health
curl -s -X POST https://YOUR-APP.fly.dev/simulate \
  -H "Content-Type: application/json" \
  -d '{"seed":42,"steps":1000}'

7) Basic operations + troubleshooting

Useful commands:

fly status
fly logs
fly ssh console
fly secrets set MY_API_KEY="..."

If your deploy succeeds but you get 502 or health check failures, it’s almost always one of these:

For FastAPI + Uvicorn on Fly, the safe default is:

8) Next steps

Once the deploy loop is solid, the next moves are:

Writing endpoints is the easy part. Building a backend you can deploy, observe, and trust is the real work. Fly + FastAPI is a solid starting point.

← Back to Blog