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
References
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.
Proposed Solution
Introduce a
DATABASE_PROVIDERenvironment 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
sqliteto keep the barrier to entry as low as possible — clone, run, done.Suggested Approach
1. Add asyncpg dependency
2. Update database initialization
Read
DATABASE_PROVIDERat startup and configure the appropriate async engine:Ensure Alembic's
env.pyfrom #2 also readsDATABASE_URLfrom the environment so migrations target the correct database.3. Update
compose.yamlUse Docker Compose profiles so the
postgresservice only starts when explicitly requested:Usage:
4. Update
.env.example5. 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, supersedingdocs/adr/0001-sqlite-as-database-engine.md. Document the decision, the two options, and whysqliteis the default.7. Update README
Add a "Database" section documenting the two modes and how to switch between them.
Acceptance Criteria
asyncpgadded topyproject.tomlDATABASE_PROVIDERenv var controls provider selection (sqlitedefault,postgresopt-in)docker compose upworks with SQLite, no PostgreSQL container startedDATABASE_PROVIDER=postgres docker compose --profile postgres upworks with PostgreSQLuv run fastapi devcontinues to work with SQLite unchangedenv.pyreadsDATABASE_URLfrom the environment.env.exampledocumentsDATABASE_PROVIDER,DATABASE_URL, andPOSTGRES_PASSWORD.envis git-ignoreddocs/adr/0011-configurable-database-provider.mdadded, supersedingdocs/adr/0001-sqlite-as-database-engine.mdREADME.mdupdatedCHANGELOG.mdupdatedReferences
docs/adr/0001-sqlite-as-database-engine.md