Skip to content

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.

Abhishek Jaiswal

6 min

On this page 9 sections

FastAPI 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, adding a health check endpoint, and turning on autoscaling.

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:

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:

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

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 in front of Uvicorn 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:

Terminal window
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:

Terminal window
okctl deploy link fastapi-app <database-id>
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:

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:

Terminal window
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 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 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 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 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 guide in the docs.

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 for the per-minute wallet model, from $5 a month. Deploy my first app.

Abhishek Jaiswal · Founder and Engineer

Abhishek Jaiswal is the founder and engineer behind Ownkube, a cloud for personal software. He builds the platform and writes here about cloud cost, migrations, and running production infrastructure in your own AWS account without a DevOps team.

Share

More posts

All posts

Ship my first app in the next five minutes.

Push a repo and get a live URL, with no cloud account or config.

Top up from $5, billed per minute with no subscription.