Skip to content

Add PostgreSQL support as configurable database provider #542

@nanotaboada

Description

@nanotaboada

Problem

The project currently uses SQLite with no option to run a different database engine. The original plan was to add PostgreSQL for Docker/production deployments, but a fixed SQLite-local / PostgreSQL-production split introduces a mixed-environment setup: different engines in dev and prod, which creates subtle behavioral differences that are hard to catch locally.

A better approach is to make the database engine fully configurable, so developers choose one provider and use it consistently — local dev, Docker, and any deployment all run the same engine.

Depends on: #2 (Implement Alembic for Database Migrations)

Proposed Solution

Introduce a DATABASE_PROVIDER environment variable that selects the database engine for the entire stack:

  • DATABASE_PROVIDER=sqlite (default): SQLite everywhere. Zero infrastructure required. Works on any machine, including legacy hardware. No Docker needed.
  • DATABASE_PROVIDER=postgres: PostgreSQL everywhere. Requires Docker. Opt-in for developers who want a server-based engine or full production parity.

The default is sqlite to keep the barrier to entry as low as possible — clone, run, done.

Suggested Approach

1. Add asyncpg dependency

# pyproject.toml
dependencies = [
    "asyncpg",
    # existing deps...
]

2. Update database initialization

Read DATABASE_PROVIDER at startup and configure the appropriate async engine:

import os

provider = os.getenv("DATABASE_PROVIDER", "sqlite")

if provider == "postgres":
    DATABASE_URL = os.getenv(
        "DATABASE_URL",
        "postgresql+asyncpg://postgres:postgres@localhost:5432/players"
    )
else:
    DATABASE_URL = "sqlite+aiosqlite:///players-sqlite3.db"

engine = create_async_engine(DATABASE_URL, echo=os.getenv("ENV") == "development")

Ensure Alembic's env.py from #2 also reads DATABASE_URL from the environment so migrations target the correct database.

3. Update compose.yaml

Use Docker Compose profiles so the postgres service only starts when explicitly requested:

services:
  web:
    environment:
      - DATABASE_PROVIDER=${DATABASE_PROVIDER:-sqlite}
      - DATABASE_URL=${DATABASE_URL:-}

  postgres:
    image: postgres:17-alpine
    profiles: [postgres]
    environment:
      POSTGRES_DB: players
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  postgres-data:

Usage:

# SQLite (default, no Docker dependency)
docker compose up

# PostgreSQL (opt-in)
DATABASE_PROVIDER=postgres docker compose --profile postgres up

4. Update .env.example

# Database provider: sqlite (default) | postgres
DATABASE_PROVIDER=sqlite

# Required only when DATABASE_PROVIDER=postgres
DATABASE_URL=postgresql+asyncpg://postgres:your_secure_password_here@postgres:5432/players
POSTGRES_PASSWORD=your_secure_password_here

5. Verify migration compatibility

Confirm that the Alembic migrations from #2 use SQL compatible with both SQLite and PostgreSQL. Address any dialect differences if found.

6. Add ADR

Add docs/adr/0011-configurable-database-provider.md, superseding docs/adr/0001-sqlite-as-database-engine.md. Document the decision, the two options, and why sqlite is the default.

7. Update README

Add a "Database" section documenting the two modes and how to switch between them.

Acceptance Criteria

  • asyncpg added to pyproject.toml
  • DATABASE_PROVIDER env var controls provider selection (sqlite default, postgres opt-in)
  • docker compose up works with SQLite, no PostgreSQL container started
  • DATABASE_PROVIDER=postgres docker compose --profile postgres up works with PostgreSQL
  • uv run fastapi dev continues to work with SQLite unchanged
  • Alembic migrations from Implement Alembic for database migrations #2 apply cleanly to both providers on startup
  • Alembic env.py reads DATABASE_URL from the environment
  • All CRUD operations work identically with both providers
  • .env.example documents DATABASE_PROVIDER, DATABASE_URL, and POSTGRES_PASSWORD
  • .env is git-ignored
  • docs/adr/0011-configurable-database-provider.md added, superseding docs/adr/0001-sqlite-as-database-engine.md
  • README.md updated
  • CHANGELOG.md updated
  • All existing tests pass

References

Metadata

Metadata

Assignees

Labels

containersPull requests that update containers codeenhancementNew feature or requestpriority:highImportant for production readiness. Schedule for current milestone.pythonPull requests that update Python code

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions