PostgreSQL is a reliable database, and FastAPI has emerged as one of the most popular frameworks for creating high-performance APIs. However, maintaining your own Postgres instance can be a distraction when it comes time to deploy. This is where the managed cloud database service Aiven comes into play. It takes away infrastructure issues and gives you complete access to PostgreSQL, making operations much less painful.
In this tutorial, I'll walk you through setting up a FastAPI project, connecting it to Aiven PostgreSQL with asyncpg, and containerizing everything with Docker for both development and production. Along the way, you'll discover how each component fits into a contemporary backend stack and the reasons these tools are frequently used together.
Requirements:
- Python 3.9+
- Docker & Docker Compose (for reproducible environments and easy deployment)
- An Aiven account (free tier works and is enough for testing)
- Basic familiarity with FastAPI and async/await (understanding non-blocking I/O will help)
Step 1: FastAPI Project Setup
Create a new project folder and set up a virtual environment:
mkdir myfastapi && cd myfastapi
python -m venv venv
source venv/bin/activate
Install dependencies:
# requirements.txt
fastapi
uvicorn[standard]
asyncpg
python-dotenv
gunicorn
email-validator
python-multipart
Here’s what these packages do:
- FastAPI - the web framework
- uvicorn - ASGI server for running FastAPI
- asyncpg - high-performance async PostgreSQL driver
- python-dotenv - loads environment variables from .env
- gunicorn - production-grade process manager
- others → useful utilities for forms and validation
Now, a minimal FastAPI app with a health check and a database test endpoint:
# app/main.py
from fastapi import FastAPI
import asyncpg
import os
app = FastAPI()
DATABASE_URL = os.getenv("DATABASE_URL")
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/db-check")
async def db_check():
conn = await asyncpg.connect(DATABASE_URL)
version = await conn.fetchval("SELECT version()")
await conn.close()
return {"postgres_version": version}
This example demonstrates:
- A basic API endpoint (
/) - A real database connection (
/db-check) - Async database access using
await, which allows FastAPI to handle many requests efficiently without blocking
Step 2: Configure Aiven PostgreSQL
- Log in to Aiven Console, create a new PostgreSQL service (choose your cloud provider and region).
- Once the service is running, go to the Overview tab and copy the Service URI. It will look like:
postgres://avnadmin:password@host:port/defaultdb?sslmode=require
- Important: For asyncpg, the URI must use the
postgresql+asyncpg://scheme. Adjust it to:
postgresql+asyncpg://avnadmin:password@host:port/defaultdb?sslmode=require
This tells your app to use the asyncpg driver instead of the default PostgreSQL driver.
Save this as DATABASE_URL in a .env file (but remember, never commit it!).
Step 3: Dockerize the App
Create a Dockerfile:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Docker lets you package your app with all its dependencies into a container, ensuring:
- consistent behavior across environments
- easy deployment to cloud platforms
- no “works on my machine” issues
Build the image: docker build -t myfastapi .
Step 4: Docker Compose for Development
docker-compose.yml (dev version):
version: "3.9"
services:
app:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: ${DATABASE_URL}
volumes:
- ./app:/app/app # live reload for code changes
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
Key ideas here:
- Volumes allow live code updates without rebuilding the container
- --reload enables hot-reloading for faster development
- Environment variables keep secrets out of your code
Run with: docker compose --env-file .env up
Step 5: Running SQL Scripts on Aiven
Since we are not using a local database container, we execute scripts directly against Aiven using psql:
psql "$DATABASE_URL" < scripts/01_create_tables.sql
What this approach does:
- treats Aiven as your single source of truth
- avoids environment drift between local and production
- is closer to how real production systems are managed
Make sure your DATABASE_URL in the shell uses the postgresql://scheme (without +asyncpg) for psql.
Step 6: Docker Compose for Production
Create docker-compose.prod.yml:
version: "3.9"
services:
app:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: ${DATABASE_URL}
command: gunicorn -w 4 -k uvicorn.workers.UvicornWorker app.main:app --bind 0.0.0.0:8000
restart: always
Differences from development:
- No volume mounts (immutable containers)
- Uses Gunicorn for better process management
- Multiple workers for handling concurrent traffic
Run in detached mode:
docker compose --env-file .env.production -f docker-compose.prod.yml up -d
Step 7: Environment Variables & Security
Never commit .env files. Use .env.example as a template.
In production, inject secrets via your CI/CD or orchestration tool.
Always use sslmode=require with Aiven to enforce encrypted connections.
Conclusion
You now have a FastAPI app running with a managed PostgreSQL database, fully containerized and ready for production. This setup lets you focus on writing code while Aiven handles the database heavy lifting.
If you found this helpful, give it a ❤️ and share it with your network.
Top comments (0)