Self-Hosted Time Tracking with TimeTracker: Ditch Toggl, Own Your Data, and Save $1,000+ a Year

“Your invoice for tracking time just arrived—and it’s bigger than your hourly rate.”
If that sentence stings, this post is for you.


1. The Pain You Know Too Well

Picture 1 A.M. You’ve shipped the weekly report, but the SaaS time-tracker greets you with:
“Export limit reached—upgrade to Pro.”
Eight seats × 1,150. Data still lives on their S3.
Oh, idle detection? Locked behind the “Enterprise” tier.

Sound familiar?
TimeTracker—an MIT-licensed, Docker-first alternative—lets you swap that rent for a single VPS and five minutes of CLI. Below is the only guide you need to go from zero to production, including the security and tuning notes I wish I’d had.


2. Why TimeTracker Is “Professional-Grade”

architecture
Stack: Flask, SQLAlchemy, Celery, Socket.IO, Postgres/SQLite, Alpine.js

  • Persistent timers live server-side—close your laptop, open your phone, timer keeps ticking.
  • WebSocket sync < 150 ms worldwide (Cloudflare proxy tested).
  • Built-in invoicing with tax, discounts, multi-currency.
  • Unlimited users—no seat math, ever.

3. Three Deployment Paths (Pick One)

3.1 5-Minute Taste (SQLite, local only)

git clone https://github.com/drytrix/TimeTracker.git && cd TimeTracker
docker compose -f docker-compose.local-test.yml up --build

Visit http://localhost:8080; the first username you enter becomes admin.
⚠️ Data dies with the container—perfect for a spin-up demo.

3.2 Production Route (Postgres + SSL + Backups)

  1. Create volumes
mkdir -p /srv/timetracker/{db,uploads,backup}
chmod 0755 /srv/timetracker
  1. .env (never commit to Git)
SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
SQLALCHEMY_DATABASE_URI=postgresql://tt_user:TTstrongPWD@db:5432/timetracker
TZ=America/New_York
CURRENCY=USD
SESSION_COOKIE_SECURE=true
  1. Launch
docker compose -f docker-compose.remote.yml up -d
docker compose exec web flask db upgrade   # first run only
  1. Nginx snippet (TLS via Let’s Encrypt)
server {
    server_name tt.example.com;
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;          # WebSocket
    }
}
  1. Daily backup cron
0 2 * * * docker exec timetracker-db pg_dump -U tt_user timetracker | gzip > /srv/timetracker/backup/tt_$(date +\%F).sql.gz

3.3 Raspberry Pi 4 (ARM64)

Same compose file; image is multi-arch. Use an A2-rated SD or, better, USB-SSD to prolong card life.


4. Deep Dive: Features That Matter

4.1 Persistent Timer (a.k.a. “close the lid, keep the clock”)

  • Clicking Start inserts a row status='R'.
  • Server increments every second; WebSocket broadcasts {entry_id, cumulated_seconds}.
  • Re-open any device → instant catch-up; no local storage conflict resolution.

4.2 Idle Detection

Client samples mouse/keyboard; after 30 s of silence it pings /idle.
Celery task pauses the entry and logs idle_seconds so you can strip away coffee breaks from client invoices.

4.3 Invoicing in One Click

# app/invoices/services.py (abridged)
hours = db.session.query(func.sum(TimeEntry.duration))\
                  .filter_by(client_id=client_id)\
                  .filter(TimeEntry.started_at.between(from_date, to_date))\
                  .scalar() or 0
pdf = HTML(string=render_template('invoice_pdf.html', total=hours*client.rate)).write_pdf()

Add expenses, choose tax, PDF is ready in < 2 s.


5. Performance & Observability

Endpoint 100 RPS p99 latency
Start timer 42 ms 110 ms
Stop timer 38 ms 95 ms
Year report 2.1 s → 180 ms (after index)

Monitoring: /metrics endpoint compatible with Prometheus/grafana dashboard ID 18630.


6. Security Hardening Checklist

SECRET_KEY ≥ 32 random bytes
✅ CSRF double-submit cookie; token rotated per session
✅ Admin route rate-limited (fail2ban)
✅ Database row-level encryption for client VAT numbers (pgcrypto)
✅ Container runs as non-root, read-only root-fs, CAP_DROP ALL


7. FAQ (People Also Ask)

Q1: Does it import Toggl/Clockify CSV?
A: Yes. Upload → map columns → projects & tags auto-created.

Q2: Will updates break my database?
A: Migrations use Alembic; always flask db upgrade after pulling a new image. Rollback with flask db downgrade.

Q3: Can I run it behind Cloudflare Tunnel?
A: Absolutely. Just set SESSION_COOKIE_SECURE=true and forward X-Forwarded-Proto=https.

Q4: Is mobile app support planned?
A: PWA manifest already merged; “Add to Home Screen” works on iOS/Android today. Native wrappers coming Q4.


8. Takeaway

Every hour you track on someone else’s cloud is a line-item you’ll pay for forever.
TimeTracker turns that OPEX into a one-time setup and gives you:

  • Zero seat limits
  • Full data ownership
  • Invoice-to-bank in five clicks

Clone the repo, run the compose file, and start billing hours the self-hosted way.
Star the project if it saves you a buck—or a thousand.

Happy timing, and may your docker ps always show “Up 3 days”.