Fix “invalid literal for int() with base 10: 'None'” in FastAPI (Asyncpg + Docker)

Published: (March 17, 2026 at 11:59 PM EDT)
3 min read
Source: Dev.to

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.

0 views
Back to Blog

Related posts

Read more »

Stop Leaking Data in Multi-Tenant Apps

Why Your application logic isn't Enough: The Case for Database-Level Row-Level Security You've built a robust multi‑tenant SaaS. You've implemented tenant_id f...