# How to deploy a FastAPI app on Ownkube

[FastAPI](https://fastapi.tiangolo.com/) is fast to run locally with `uvicorn app.main:app --reload`, and the production version of that command is not much longer. Production adds three things: a process manager that restarts a crashed worker, config read from environment variables instead of a `.env` file in the repo, and a health check for the platform to poll. Ownkube handles the rest from a git push.

This walks through deploying a FastAPI app on Ownkube Compute: running Gunicorn with Uvicorn workers, configuring settings through [environment variables](https://ownkube.io/blog/manage-secrets-env-vars), adding a health check endpoint, and turning on [autoscaling](https://ownkube.io/blog/autoscale-cpu-memory).

**TL;DR**

- Push a FastAPI app with a `Dockerfile`, and Ownkube builds it, deploys it, and issues TLS automatically on your `*.ownkube.app` hostname.
- Run Gunicorn with `uvicorn.workers.UvicornWorker` in production rather than Uvicorn's dev server directly, so a crashed worker restarts instead of taking the app down.
- Load configuration through a Pydantic `BaseSettings` class and set the real values as environment variables in the dashboard, never in the repo.
- Add a Postgres database only if the app needs one; a stateless API can skip it entirely.
- Expose a `/health` route and turn on horizontal autoscaling once traffic is uneven.

## 1. Structure the app for a container

A typical FastAPI project layout works as-is:

```
app/
  __init__.py
  main.py
  config.py
  routers/
requirements.txt
Dockerfile
```

Load configuration through `pydantic-settings` so every value has a typed default and a clear environment variable name:

```python
# app/config.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str | None = None
    environment: str = "production"
    cors_origins: list[str] = []

    class Config:
        env_file = None  # read only from real environment variables in production

settings = Settings()
```

Add a health check route, a few lines that give Ownkube something reliable to check the container against:

```python
# app/main.py
from fastapi import FastAPI
from app.config import settings

app = FastAPI()

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

## 2. Write a Dockerfile

```dockerfile
FROM python:3.12-slim

WORKDIR /app

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

COPY . .

ENV PYTHONUNBUFFERED=1
EXPOSE 8000

CMD ["gunicorn", "app.main:app", \
     "--worker-class", "uvicorn.workers.UvicornWorker", \
     "--bind", "0.0.0.0:8000", \
     "--workers", "4", \
     "--timeout", "60"]
```

[Gunicorn](https://docs.gunicorn.org/) in front of [Uvicorn](https://www.uvicorn.org/) workers is the standard production pairing: Gunicorn manages the worker pool and restarts a worker that dies, and each worker runs Uvicorn's ASGI event loop. Running `uvicorn` directly with no process manager is fine for a demo. A container that needs to survive a bad request or an unhandled exception wants the supervisor.

Add `requirements.txt`:

```
fastapi
uvicorn[standard]
gunicorn
pydantic-settings
```

## 3. Push the repo and create the app

Connect the repository from the Ownkube dashboard (`app.ownkube.io`) to deploy on every push, or deploy the current directory with the `okctl` CLI:

```bash
okctl login
okctl up --name fastapi-app --port 8000
```

Ownkube builds the image, allocates a hostname on `*.ownkube.app`, and issues TLS automatically. If the repo has no Dockerfile, Ownkube's automatic build detects Python and builds an image for you, though a Dockerfile gives more control over the worker command above.

## 4. Add a managed Postgres database, if you need one

Plenty of FastAPI services are stateless: they call out to another API and return a response, and nothing needs to persist. If that describes your app, skip this step.

If the app does need a database, create a database box from the dashboard: **New > Database > Postgres**. It provisions a private (in-region) Postgres box starting at $4 a month plus storage at $0.25 per GB, with backups and point-in-time recovery available on Personal and Team. The database runs as a single instance by default; opt-in public access and, on Team, high availability are per-database options.

Link the database to the app. Ownkube writes its connection string into the app as a secret `DATABASE_URL` variable and redeploys it. Read it through settings:

```bash
okctl deploy link fastapi-app <database-id>
```

```python
# app/config.py
class Settings(BaseSettings):
    database_url: str | None = None
```

The linked URL starts with `postgresql://`, and SQLAlchemy's async engine wants the `postgresql+asyncpg://` scheme, so swap it when you create the engine:

```python
from sqlalchemy.ext.asyncio import create_async_engine

engine = create_async_engine(
    settings.database_url.replace("postgresql://", "postgresql+asyncpg://", 1),
    pool_pre_ping=True,
)
```

## 5. Set your configuration as environment variables

Anything the Pydantic `Settings` class reads should come from the dashboard instead of a committed `.env` file:

```bash
okctl vars set fastapi-app ENVIRONMENT=production CORS_ORIGINS="https://yourapp.com"
```

Environment variables set through Ownkube are encrypted at rest and only injected into the running container, so secrets like API keys or a database password never live in the repo or the image.

## 6. Scale and watch it run

A fresh FastAPI service usually starts fine on a small metered ceiling, for example a 500m CPU limit and 512 MiB of memory, set on the sliders under **Settings → Resources**.

Turn on horizontal autoscaling once traffic is uneven, so Ownkube adds Uvicorn worker instances under load and scales back down when it is quiet. On Ownkube Compute you turn it on through your coding agent (the `update_deployment` MCP tool) or the API, with a minimum and maximum replica count and a CPU target such as 70%.

Metered billing suits an API that idles between bursts: it charges CPU and memory on actual per-minute usage, so a service that sits quiet overnight barely draws the wallet down.

If the service instead runs hot around the clock, for example a worker consuming a queue continuously, a flat Spark box is a steadier price than metering a constant load. Logs and metrics for the app live in the dashboard, so a worker that keeps restarting or a slow endpoint shows up in one place.

## Moving to your own AWS later

If the service needs to run inside your own or a customer's AWS account, the same Dockerfile and FastAPI app move there with no rewrite. Ownkube provisions the instance, wires up the same deploy flow, and brings the logs, metrics, and health checks with it. Railway only runs on its own infrastructure. Our [Railway alternatives guide](https://ownkube.io/compare/railway-alternatives) compares Railway with the other options.

## FAQ

### Should I use Uvicorn directly or Gunicorn with Uvicorn workers?

Use Gunicorn with `uvicorn.workers.UvicornWorker` in production. Gunicorn manages the worker pool, restarts a worker that crashes, and gives you a `--workers` count to tune; running Uvicorn alone has none of that supervision.

### Does Ownkube support WebSockets?

Yes, a Uvicorn worker handles WebSocket connections the same way it does in any other deployment. Keep in mind that autoscaling adds new instances rather than new connections to an existing one, so a long-lived WebSocket workload should size `--workers` and the autoscale ceiling with connection count in mind as well as CPU.

### How does Ownkube know my app is healthy?

Health checks on the app's **Settings** tab poll `/health` by default, and Ownkube restarts the container when the liveness check keeps failing. A lightweight `/health` route that does not touch the database gives it a fast, reliable signal, separate from a `/status` endpoint that might check dependencies and take longer to answer.

### What happens if my wallet balance runs out mid-month?

The app pauses and any attached database keeps its data. Adding credit or the next monthly plan load resumes it. Unused wallet credit rolls over and never expires.

### Can I run a background worker alongside the API?

Yes. Deploy it as a second Ownkube app or as a [scheduled job](https://ownkube.io/blog/run-scheduled-cron-jobs) if the work runs on a cadence rather than continuously, and point it at the same Postgres connection string if it shares data with the API.

For the always-current version of this walkthrough, see the [Deploy FastAPI](https://ownkube.io/docs/deploy/fastapi) guide in the docs. [Codex](https://ownkube.io/agents/codex) and [Windsurf](https://ownkube.io/agents/windsurf) can also run the deploy through Ownkube's MCP server, from the same session where you wrote the API.

## Where Ownkube fits

FastAPI wants a process manager that survives a crashed worker, config that stays out of the repo, and a place to add a database only when the app actually needs one, and Ownkube gives you all three without provisioning a cloud account first. Push the Dockerfile, set your environment variables, and turn on autoscaling when traffic gets uneven.

See [Ownkube pricing](https://ownkube.io/pricing) for the per-minute wallet model, from $5 a month. [Deploy my first app](https://app.ownkube.io/login).
