The Problem

Most habit trackers force a "goals" model—streaks to maintain, targets to hit—which adds pressure and guilt. Beaver Habit Tracker strips that out: you just mark days done or not, and the app shows you the pattern. It's a self-hosted alternative to SaaS trackers, giving you full control over your data and no subscription.

What This Does

This is a Flask-based web app (Python, 71 files) that renders a server-side UI with JavaScript enhancements. The core loop is simple: you define habits, mark days complete, and the app renders calendars, streaks, and stats. Storage is pluggable—SQLite via SQLAlchemy (beaverhabits/app/db.py) or JSON files (beaverhabits/storage/dict.py).

The frontend is generated in Python (beaverhabits/frontend/components.py, 94 functions) rather than a JS framework. There's also a demo mode, user auth with API tokens, and a paid tier via Paddle (beaverhabits/plan/paddle.py). The statics/ folder holds PWA assets and vendor JS.

How It Is Wired

Execution starts in beaverhabits/main.py at the lifespan function, which reaches 17 functions. From there, the app initializes the database and starts a scheduler (beaverhabits/scheduler.py) that runs a daily backup task. The backup path is short: lifespan -> daily_backup_task -> backup_all_users -> get_user_habit_list, which hits the database via session.execute. So a single run touches the DB within 4 hops.

The request path is framework-driven (Flask routes in beaverhabits/routes/routes.py), which the static call graph can't see. But once a handler runs, everything funnels through beaverhabits/storage/storage.py (54 functions, 24 modules depend on it) and beaverhabits/app/crud.py (18 functions, 15 callers). The most-connected module is beaverhabits/configs.py—26 modules import it, making it a high-blast-radius hub. The beaverhabits/frontend/layout.py module sits in a circular import cycle with menu.py, which will complicate any refactor of the navigation.

How To Use It

Setup uses uv:

uv venv && uv sync
./start.sh dev

For Docker:

docker run -d --name beaverhabits \
  -u $(id -u):$(id -g) \
  -e HABITS_STORAGE=USER_DISK \
  -v ./beaver/:/app/.user/ \
  -p 8080:8080 \
  daya0576/beaverhabits:latest

Configuration is via environment variables: HABITS_STORAGE (DATABASE or USER_DISK), TRUSTED_LOCAL_EMAIL to skip auth, INDEX_HABIT_DATE_COLUMNS for layout. The container runs as nobody for security.

Real-World Use

A home server running the Docker container, with habits stored as a local JSON file. The USER_DISK storage mode means no database to manage—just a mounted volume. A user opens the PWA on their phone, marks "read" for the day, and the data persists to the mounted folder. Backups are automatic via the scheduler. The demo mode (beaverhabits.com/demo) shows the full UI without setup.

Code Health & Issues

Static analysis found 31 issues (6 high, 25 medium). Key findings:

  • High - Circular imports in beaverhabits/frontend/layout.py and menu.py—mutually reachable modules make changes risky.
  • High - Deep nesting (depth 6) in layout.py, storage/dict.py, and components.py—control flow is hard to follow.
  • High - Hub modules: configs.py has 26 dependents, storage.py has 24—changes ripple widely.
  • Medium - Broad exception handling in components.py, auth.py, menu.py—errors get swallowed.
  • Medium - Unclosed file handles in layout.py, javascript.py, export_page.py.

SDLC observations: CI exists (GitHub Actions), but workflows don't pin actions to commit SHAs (a security risk), lack least-privilege permissions, and have no dependency vulnerability scan. The Docker base image python:3.14-slim is unpinned by digest. No lockfile for Python deps means non-reproducible builds.

The Bottom Line

This is a functional, self-hosted habit tracker with a clean UI and sensible storage abstraction. The codebase is mature enough for personal use, but the circular imports and hub modules will make significant refactoring painful. Use it if you want a no-frills tracker you control; plan for a security pass on CI before exposing it publicly.