TimeTracker
Self-hosted time tracking and invoicing for freelancers and teams
Alternative to: toggl, harvest, clockify
TimeTracker is a self-hosted, web-based time tracking application built for freelancers, teams, and agencies. It combines timers and project/task management with a Kanban board, client invoicing with PDF and Peppol/ZUGFeRD e-invoicing, expense tracking, and detailed reports, plus OIDC/SSO login and a REST API.
TimeTracker Docker Compose example
Self-host TimeTracker on your own server, homelab, or VPS starting from this Docker Compose example.
It runs TimeTracker in Docker containers using the official drytrix/timetracker:latest, postgres:16-alpine images, with persistent volumes and automatic restarts preconfigured.
Review the environment variables and adjust them to your setup, save the file as compose.yml (or docker-compose.yml), and start the stack with docker compose up -d.
services:
app:
image: drytrix/timetracker:latest
restart: unless-stopped
environment:
# Timezone used by the application.
TZ: "UTC"
# Default currency for invoices and reports (ISO 4217 code).
CURRENCY: "EUR"
# Comma-separated usernames that are granted admin rights.
ADMIN_USERNAMES: "admin"
# Allow visitors to create their own accounts (true/false).
ALLOW_SELF_REGISTER: "false"
# Secret key used to sign sessions and CSRF tokens. Use a random 32+ character string.
SECRET_KEY: "3bd99edcb83dfb603ea4ba585a3e3f49c8c886fb6f7234d94a57a6239510970f"
# Public URL where the app is reached (e.g. https://time.example.com).
APP_BASE_URL: "changeme"
# Comma-separated origins trusted for CSRF-protected form submissions; include your public URL.
WTF_CSRF_TRUSTED_ORIGINS: "changeme"
# Scheme used when generating absolute URLs (https behind a TLS-terminating reverse proxy).
PREFERRED_URL_SCHEME: "https"
# Connection string for the PostgreSQL database (internal service).
DATABASE_URL: "postgresql+psycopg2://timetracker:timetracker@db:5432/timetracker"
# Redis is optional and disabled by default (sessions are stored server-side).
REDIS_ENABLED: "false"
volumes:
- app_data:/data
- app_logs:/app/logs
- app_uploads:/app/app/static/uploads
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "-s", "-o", "/dev/null", "http://localhost:8080/_health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
# Database name created on first boot.
POSTGRES_DB: "timetracker"
# Database user created on first boot.
POSTGRES_USER: "timetracker"
# Database password (internal to the compose network).
POSTGRES_PASSWORD: "timetracker"
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U timetracker -d timetracker"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
volumes:
app_data:
app_logs:
app_uploads:
db_data:
Values set to changeme are required — replace them with your own
values before starting TimeTracker.
Prefer a managed setup? WinterFlow installs, configures, and updates TimeTracker for you using this same Docker Compose configuration.