Fix “invalid literal for int() with base 10: 'None'” in FastAPI (Asyncpg + Docker)
Source: Dev.to
Problem Overview
When deploying a FastAPI application with Aiven PostgreSQL in Docker, you may encounter the following traceback:
ValueError: invalid literal for int() with base 10: 'None'
SQLAlchemy’s URL parser interprets the string -None as the port portion of the database URL, which appears as:
postgresql+asyncpg://user:pass@host:None/dbname
Cause
SQLAlchemy’s make_url() function tries to convert the port component to an integer. If a variable used to build the URL evaluates to None, the f‑string interpolation turns it into the literal string "None", leading to the error.
Typical code pattern:
import os
DB_USER = os.getenv("DB_USER")
DB_PASSWORD = os.getenv("DB_PASSWORD")
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT")
DB_NAME = os.getenv("DB_NAME")
ASYNC_DB_URL = f"postgresql+asyncpg://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
engine = create_async_engine(ASYNC_DB_URL)
If DB_PORT (or any other component) is missing in the container’s environment, it becomes None, producing a malformed URL with :None.
Fallback logic that mixes a full DATABASE_URL with component‑based construction can also trigger the issue:
ASYNC_DB_URL = os.getenv("DATABASE_URL") or f"postgresql+asyncpg://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
When DATABASE_URL is absent, the constructed URL inherits the None port.
Solution
Use a Single DATABASE_URL
Prefer a single environment variable that contains the complete connection string. Validate its presence early and avoid building the URL from separate components.
import os
from sqlalchemy.ext.asyncio import create_async_engine
DATABASE_URL = os.getenv("DATABASE_URL")
if not DATABASE_URL:
raise ValueError("DATABASE_URL environment variable is not set")
engine = create_async_engine(DATABASE_URL)
Adjust the Scheme for asyncpg
Aiven (and many cloud providers) supply URLs with the postgres:// scheme. For SQLAlchemy async support, replace it with postgresql+asyncpg://.
raw_url = os.getenv("DATABASE_URL")
if raw_url and raw_url.startswith("postgres://"):
raw_url = raw_url.replace("postgres://", "postgresql+asyncpg://", 1)
engine = create_async_engine(raw_url)
Full Example (database.py)
import os
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker, declarative_base
# Retrieve and validate the full URL
DATABASE_URL = os.getenv("DATABASE_URL")
if not DATABASE_URL:
raise ValueError("DATABASE_URL environment variable is required")
# Ensure the asyncpg driver scheme
if DATABASE_URL.startswith("postgres://"):
DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql+asyncpg://", 1)
# Create the async engine
engine = create_async_engine(DATABASE_URL, echo=True)
# Session factory
AsyncSessionLocal = sessionmaker(
bind=engine,
class_=AsyncSession,
expire_on_commit=False,
)
# Base class for models
Base = declarative_base()
Docker Configuration
Pass the DATABASE_URL to the container via an .env file or directly in docker‑compose.yml.
services:
app:
build: .
env_file:
- .env
.env example:
DATABASE_URL=postgresql+asyncpg://avnadmin:password@host:port/defaultdb?sslmode=require
Best Practices
- Single source of truth: Store the full connection string in one variable.
- Early validation: Raise clear errors if required variables are missing.
- Defensive programming: Avoid constructing URLs from fragmented components.
- Consistent scheme: Use
postgresql+asyncpg://when working with SQLAlchemy async. - Minimal environment variables: Reduces the risk of mismatched or missing values across environments.
By consolidating configuration into a single, validated DATABASE_URL, you eliminate the None port error and simplify deployment across Docker, Kubernetes, or any other platform.