# How to deploy a FastAPI app on Ownkube

> Deploy a FastAPI app on Ownkube behind Gunicorn and Uvicorn workers, with a health check endpoint, an optional managed Postgres, and autoscaling under load.

- **Published:** 2026-08-26
- **Author:** Ownkube team
- **Category:** How-To
- **Tags:** fastapi, python, deploy, tutorial, ownkube-compute
- **Canonical URL:** https://ownkube.io/blog/deploy-fastapi-app
- **Cover:** https://ownkube.io/blog/deploy-fastapi-app.png

---
[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. What changes is running behind a process manager that restarts a crashed worker, wiring config through environment variables instead of a `.env` file in the repo, and giving the platform a health check 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](/blog/manage-secrets-env-vars), adding a health check endpoint, and turning on [autoscaling](/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; nothing about the layout is Ownkube-specific:

```
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. It costs a few lines and gives 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 works for a demo, not for a container that needs to survive a bad request or an unhandled exception.

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`) or push directly with the `okctl` CLI:

```bash
okctl login
okctl apps create fastapi-app --dockerfile Dockerfile
okctl deploy
```

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 single-instance, private (in-region) Postgres box starting at $4 a month plus storage at $0.15 per GB, with backups included. Multi-instance HA and public access are on the roadmap, not shipped yet.

Attach the connection string as an environment variable and read it through settings:

```bash
okctl env set DATABASE_URL="postgresql+asyncpg://user:password@db-host:5432/fastapi_production" --app fastapi-app
```

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

With SQLAlchemy's async engine, point it straight at `settings.database_url`:

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

engine = create_async_engine(settings.database_url, pool_pre_ping=True)
```

## 5. Set your configuration as environment variables

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

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

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:

```bash
okctl deploy --cpu-limit 500m --memory-limit 512Mi
```

Turn on horizontal autoscaling once traffic is uneven, so Ownkube adds Uvicorn worker instances under load and scales back down when it is quiet:

```bash
okctl apps update fastapi-app --autoscale-min 1 --autoscale-max 5 --autoscale-cpu-target 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 or Core 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 a customer's AWS account, or you outgrow the shared Compute tier, the same Dockerfile and FastAPI app move to your own AWS with no rewrite. Ownkube provisions the instance, wires up the same deploy flow, and brings the logs, metrics, and health checks with it, which is a path a platform like Railway does not offer since it only runs on its own infrastructure. Our [Railway alternative comparison](/blog/railway-alternative-2026) goes into where that matters most.

## 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, not just CPU.

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

Ownkube watches the container and restarts it if the process stops responding. Exposing 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](/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.

## 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. [Deploy your first app](https://app.ownkube.io/login).