FastAPI 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, 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.apphostname. - Run Gunicorn with
uvicorn.workers.UvicornWorkerin production rather than Uvicorn’s dev server directly, so a crashed worker restarts instead of taking the app down. - Load configuration through a Pydantic
BaseSettingsclass 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
/healthroute 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:
# 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:
# 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 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:
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:
okctl env set DATABASE_URL="postgresql+asyncpg://user:password@db-host:5432/fastapi_production" --app fastapi-app
# app/config.py
class Settings(BaseSettings):
database_url: str | None = None
With SQLAlchemy’s async engine, point it straight at settings.database_url:
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:
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:
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:
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 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 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.