TimeTracker: self-hosted time tracking with Peppol invoicing
Self-hosted time tracking & invoicing for freelancers and teams: timers, projects, PDF and Peppol e-invoicing. Open-source Toggl/Clockify alternative.
At a glance
- What is it?
- DRYTRIX TimeTracker is a self-hosted Flask application for timers, projects, timesheets and invoicing, with a Peppol Bridge for EN 16931 e-invoicing, PDF generation and a browser extension, aimed at freelancers and teams who want their time data on their own server without per-seat pricing. It is a large, fast moving GPL-3.0 project with a 130 plus feature list.
- Who is it for?
- Adopt TimeTracker if you bill by the hour and want your time data, your clients and your invoices on infrastructure you control, especially if a client requires EN 16931 or Peppol e-invoicing, because the bundled bridge covers that without a paid intermediary.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The quick start is four commands and no clone
The installation pitch is a sixty second quick start, and the reason it can be that short is that the compose file and the database are bundled.
curl -fsSLO https://raw.githubusercontent.com/drytrix/TimeTracker/main/docker-compose.nas.yml
echo "SECRET_KEY=$(openssl rand -hex 32)" > .env
docker compose -f docker-compose.nas.yml up -dThere is no git clone and no certificate warning, and the readme says so directly: plain HTTP on port 8080 with bundled PostgreSQL. The first login creates the admin account, so the bootstrap is the compose file plus a generated secret.
That last line is the one detail worth not skipping. The SECRET_KEY is generated with openssl rand -hex 32 and written into a.env file, and it is the key that signs sessions and anything else the app signs. Generating it at install time rather than shipping a default is the right call, and the reason it is in the quick start rather than in a configuration document is that a time tracking app that holds your clients' names and billing rates should never start with a well known secret.
More install paths are offered, covering HTTPS production, a NAS UI paste option and cloud deployments, and the repository contains the corresponding files: docker-compose.yml, docker-compose.example.yml and docker-compose.nas.yml, plus a fly.toml, a railway.toml and a render.yaml for the platform as a service routes, an nginx directory, and a Dockerfile.
The licence is GPL-3.0, the language is Python, the homepage is timetracker.drytrix.com with a live demo at timetracker-demo.drytrix.com, and the recent releases are v5.18.2 from 2026-10-13, v5.18.1 from 2026-10-11 and v5.18.0 from 2026-10-04, with the last push on 2026-10-22 and the default branch develop.
Peppol and EN 16931, which is the part the hosted tools sell
The feature that distinguishes this from a Toggl or Clockify clone is e-invoicing compliance, and it comes in two forms.
There is a bundled Peppol Bridge, documented separately under the admin configuration for Peppol, which lets you send invoices yourself. The readme's framing is that there is no paid SaaS middleman for e-invoicing. And for the PDF side, Factur-X and ZUGFeRD XML can be embedded in the generated PDFs, which is how a hybrid invoice carries structured invoice data alongside a human readable document.
That is a real and somewhat unglamorous problem. European public buyers increasingly require e-invoicing under the EN 16931 standard and the Peppol network, and the commercial time tracking products generally do not do it, leaving a freelancer to find a separate tool and reconcile it by hand.
The PDF stack behind the invoices is also worth naming, because PDF generation is where a Flask application usually turns fragile. The requirements pin WeasyPrint at 70.0 alongside pydyf at 0.12.1, Pillow at 12.3.0, reportlab at 4.0.7 and pikepdf at 8.0.0 or newer. WeasyPrint is a CSS based renderer, which is what makes embedding Factur-X XML in a styled invoice document practical, and the coverage configuration even omits app/utils/pdf_generator.py and its fallback from the coverage report, which suggests the project treats PDF generation as a separate, hard to test concern.
There is also OCR in the stack, pytesseract at 0.3.10, under a comment about receipt scanning, so expenses can be captured from a photograph rather than typed.
One hundred and thirty features across thirteen categories
The readme claims 130 plus features across 13 major categories, with a complete features document linked, and the visible sections show what that means in practice.
Time tracking is the core. One-click timers, manual entry with notes and tags, bulk entry for consecutive days with weekend skipping, entry templates you can save and reuse, a calendar view, Pomodoro style focus sessions, recurring weekly time blocks, configurable rounding intervals, and idle detection that pauses automatically after a configurable period. Persistent timers keep running after the browser closes, which the comparison table calls out as the difference from browser-dependent trackers, and real-time updates across devices come over WebSocket.
Project management is unusually broad for a time tracker. Unlimited projects and tasks, client management with contacts and billing rates, a task board with priorities and assignments, a Kanban board with custom columns, issue and bug tracking with a full lifecycle, status tracking, budget tracking with alerts and forecasting, project costs, threaded task comments, Markdown in descriptions, and project favourites.
Then there is a CRM and sales management section that reads like a different product: multiple contacts per client with roles and designations, a Kanban style sales pipeline, deal management with value, probability, stages and expected close dates, lead capture with scoring and one-click conversion to clients or deals, communication history across emails, calls, meetings and notes, separate activity tracking for deals and leads, and automated lead scoring from 0 to 100.
The invoicing section covers generation directly from tracked time, a complete invoice system, and custom line items, and the quick links point to separate guides for financial management with expenses and payments, and for reporting and analytics.
The frontend dependency list explains several of these. Chart.js for reporting, fullcalendar for the calendar, frappe-gantt for a Gantt view, konva for canvas drawing, sortablejs for drag and drop, cmdk for a command palette, flatpickr for date picking, animejs for animation, a toast-ui editor for rich text, and socket.io-client for the real-time updates. The Inter font is bundled and Font Awesome is vendored.
The stack is Flask with pinned versions and serious linting
The Python side is a Flask application with a large, precisely pinned dependency set, and the pinning style is the first thing to notice. Every core dependency is pinned to an exact version, Flask at 3.1.3, Flask-SQLAlchemy at 3.1.1, Flask-Migrate at 4.0.5, Flask-Login at 0.6.3 and Flask-SocketIO at 5.6.1.
Authentication is a menu rather than a single mechanism. Authlib at 1.6.12 and PyJWT at 2.15.0 cover OAuth and OIDC, and ldap3 at 2.9.1 covers directory authentication, so the app can be wired into an existing identity provider rather than only running its own user table.
The database layer is SQLAlchemy 2.0.23 with alembic 1.13.1 and psycopg2-binary, with psycogreen for an async friendly variant. The web server is gunicorn 23.0.0 with eventlet 0.40.3, and the security layer includes Flask-WTF, Flask-Limiter at 3.8.0 for rate limiting, cryptography at 50.0.0, markdown with bleach for sanitising rendered HTML, and pyotp for one time passwords.
The operational surface is broad. Sentry for error reporting, python-json-logger for structured logs, prometheus-client for metrics, and a full OpenTelemetry stack with the API, SDK, OTLP HTTP exporter, and instrumentation for Flask and SQLAlchemy, pinned to 0.48b0 to line up with SDK 1.27. Flask-Swagger-UI with apispec and marshmallow serves API documentation, and APScheduler handles background jobs.
Then the integrations: stripe for payments, google-api-python-client with icalendar for calendar sync, pywebpush for browser notifications, firebase-admin for mobile idle wake-up, and S3 access through boto3 for uploads. Babel and Flask-Babel handle internationalisation, with a crowdin.yml at the root for translation work.
The tooling is where the project's engineering culture shows. Black and isort with a line length of 120, pylint with a specific set of disabled messages, bandit with two documented skips and a comment explaining why each is safe, flake8, a coverage configuration that omits the PDF generator and migrations, mypy configured for Python 3.11 with warn_return_any turned off and a comment explaining that SQLAlchemy 1.x style query accessors would otherwise produce hundreds of false positives, a pre-commit configuration, and a safety policy file.
Where it does not reach, and what the project is betting on
The readme is honest in a few places, and those are the places worth reading before you commit.
The most notable is the licensing of the paid path. The project states GPL-3.0 open source, free to use, modify and run commercially, with features never locked behind a paywall, and no per-user pricing with unlimited users and projects. But it also mentions an optional one-time supporter key, which the feature list clarifies: it only hides the donate prompts. So the model is a one-off supporter purchase for cosmetic benefit, and every functional feature stays open. The repository even contains donate_hide_public.pem, which is the public key used to validate such a key.
The comparison table is the marketing framing, and it is worth reading it as a claim rather than a measurement. It sets TimeTracker against traditional trackers on self-hosting, open source, persistent timers, Docker readiness, built-in invoicing and user limits.
On the edges of the product, the repository shows more than a web application. There is a browser-extension directory, a mobile directory, a desktop directory, and a grafana, loki and prometheus set of directories alongside a promtail one, which is a monitoring stack rather than a feature. There are nginx, docker, scripts, tasks, templates, migrations and tests directories, a playwright config, and a docs directory that the readme says holds the complete features documentation.
The platform deployment files, fly.toml, railway.toml and render.yaml, tell you the project is intended to be deployed on ordinary hosting as well as on a NAS, which is consistent with a self-hosting audience.
What the repository does not contain is a large public user base claim, a benchmark, or a performance figure for the timer aggregation at scale, so if your workload is thousands of concurrent timers rather than a hundred, the readme gives you nothing to go on and you would need to try it.
The bet the project is making is visible in the versioning: patch releases on 2026-10-04, 2026-10-11 and 2026-10-13, a 5.x line, and a develop default branch. This is a project that ships often, which is good for getting fixes and bad for assuming a stable upgrade path.
Editorial conclusion
Adopt TimeTracker if you bill by the hour and want your time data, your clients and your invoices on infrastructure you control, especially if a client requires EN 16931 or Peppol e-invoicing, because the bundled bridge covers that without a paid intermediary. Do not adopt it if you need a mobile app with a large team workflow, since the browser and mobile pieces in the repository are separate and thinner than the web application, and check the 130 plus feature list against what you actually need before committing. Before you install, set a real SECRET_KEY, decide whether to run the NAS plain HTTP compose file or the production one behind HTTPS, and confirm the invoice generation path works with WeasyPrint in your container before you promise a client a PDF.
Frequently asked questions
What is DRYTRIX TimeTracker?
It is a self-hosted, web-based time tracking application for freelancers, teams and businesses, covering timers, projects, tasks, timesheets, invoicing and reporting, with a bundled Peppol Bridge for EN 16931 e-invoicing and no per-seat pricing. It is a Flask application licensed under GPL-3.0.
How do I install TimeTracker quickly?
Download the NAS compose file, write a .env containing a generated SECRET_KEY, and run docker compose up with it. The readme describes this as no git clone, no certificate warning, plain HTTP on port 8080 with bundled PostgreSQL, and the first login creates the admin account.
What can TimeTracker do about e-invoicing?
It bundles a Peppol Bridge so you can send invoices yourself without a paid intermediary, and it can embed Factur-X and ZUGFeRD XML in generated PDFs. The PDF stack uses WeasyPrint with Pillow, reportlab and pikepdf, and pytesseract is used for OCR on scanned receipts.
How does TimeTracker keep timers running and show live updates?
The timers are persistent, meaning they keep running after the browser closes because the tracking happens server-side rather than in the browser. Live timer updates across devices are delivered over WebSocket, using Flask-SocketIO on the server and socket.io-client on the frontend.
Is there a paid tier in TimeTracker?
No per-user pricing, and the readme states features are never locked behind a paywall. There is an optional one-time supporter key whose only effect is to hide the donate prompts, and the repository contains donate_hide_public.pem, the public key used to validate it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/drytrix-timetracker)Was this useful?