GTD TODOs is a local-first task application for a single laptop user. The MVP is intentionally small: FastAPI serves HTML pages, SQLite persists data, and the app is designed around GTD task states, optional due dates, recurring tasks, Markdown notes, and simple project organization.
- Install uv (e.g.
brew install uv). - Install the project with development dependencies.
uv sync- Run the app locally.
uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8080The default database URL is sqlite:////data/todo.db. For local development outside Docker, set DATABASE_URL to a writable path such as sqlite:///./data/todo.db.
The compose stack requires AUTH_SECRET_KEY. Generate one and export it (or add it to a .env file next to docker-compose.yml):
# Generate a secret
python -c "import secrets; print(secrets.token_hex(32))"
# Export it (or add AUTH_SECRET_KEY=<value> to .env)
export AUTH_SECRET_KEY=<value>Start the app with a persistent named volume mounted at /data:
docker compose up --buildStop the stack:
docker compose downThe container listens internally on port 8080 but is mapped to port 8081 on the host: http://localhost:8081.
GTD TODOs supports single-user passkey (WebAuthn) authentication. On first visit a passkey is registered, and subsequent access requires authenticating with that passkey.
| Variable | Purpose | Default |
|---|---|---|
APP_HOST |
Bind address | 0.0.0.0 |
APP_PORT |
Listen port | 8080 |
DATABASE_URL |
SQLite connection string | sqlite:////data/todo.db |
TZ |
Container timezone | (unset) |
AUTH_DISABLED |
Disable auth entirely (for local dev / existing tests) | false |
AUTH_SECRET_KEY |
Secret for signing session cookies | auto-generated |
AUTH_SESSION_MAX_AGE |
Session cookie max age in seconds | 604800 (7 days) |
WEBAUTHN_RP_ID |
WebAuthn Relying Party ID (domain) | localhost |
WEBAUTHN_RP_NAME |
Human-readable RP name | GTD TODOs |
WEBAUTHN_ORIGIN |
Expected origin for WebAuthn ceremonies | http://localhost:8080 |
Set AUTH_DISABLED=true to skip all authentication:
AUTH_DISABLED=true uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8080When no credentials exist in the database, visiting any page redirects to /auth/setup where you register a passkey. After setup, future visits go through the login flow at /auth/login.
The SQLite database file is stored in the Docker volume at /data/todo.db. Back it up by copying the file or using the export endpoints:
# Copy the database file directly
docker compose cp todo-app:/data/todo.db ./backup-todo.db
# Or export data as CSV / JSON (use port 8081 for Docker Compose)
curl -o tasks.csv http://localhost:8081/export/tasks.csv
curl -o projects.json http://localhost:8081/export/projects.jsonTo restore, copy the backup into the running container and restart:
docker compose down
docker compose cp ./backup-todo.db todo-app:/data/todo.db
docker compose upIf the container is not available for docker compose cp, you can copy
directly into the named volume's mount point on the host:
# Find the volume's mount point
docker volume inspect gtd-todos_todo_app_data --format '{{ .Mountpoint }}'
# Copy the backup there (may require sudo on Linux)
sudo cp ./backup-todo.db "$(docker volume inspect gtd-todos_todo_app_data --format '{{ .Mountpoint }}')/todo.db"See docs/api.md for full export endpoint documentation.
Run tests:
uv run pytestRun lint checks:
uv run ruff check .Run type checks:
uv run mypy appBuild the container image:
docker build .app/: FastAPI app, routes, SQLModel models, Markdown helpers, templates, and static assets.tests/: test coverage for app import, page rendering, health checks, SQLite initialization, GTD workflow routes, and service-layer logic.docs/: human-readable API and agent-integration documentation..github/: Copilot instructions, skills, and CI workflow.
