Self-hosted time tracking and absence management for small teams. Covers working hours, leave requests, approvals, and monthly reports. Data stays on your own infrastructure.
Zerf is derived from the German word Zeiterfassung — time tracking.
Four roles: employees log hours and request leave, assistants record time without a fixed weekly target or flextime account, team leads approve time sheets and absences, admins manage users, categories, holidays, and settings. Every change is recorded in an audit log.
Works on desktop and mobile.
- ⏱️ Time tracking — category-based daily entries, weekly submission, overtime visibility, and week-reopen requests for corrections after submission
- 🏖️ Absence management — vacation, sick leave, training, special leave, unpaid leave, and more, each with a request/approve/reject workflow
- ✅ Approval dashboard — submitted weeks, absence requests, and reopen requests in one view
- 📅 Team calendar — absence overview with public holiday context
- 📊 Reports & CSV export — monthly per-employee breakdowns, team-level summaries, CSV for downstream processing
- 🔔 Notifications — in-app and optional email; automated reminders when employees have unsubmitted months past a configured deadline
- 🔐 Encryption — database and backups encrypted at rest; one key covers both
- 🌍 English & German
- Passwords are hashed with Argon2id; accounts lock after repeated failed attempts
- Sessions use cryptographically random tokens with idle and absolute timeouts
- The database and all backups are encrypted at rest with a single key you control
- Every change is recorded in an audit log
- The database is not reachable from outside the Docker network
See SECURITY.md for technical details.
Detailed usage guidance and workflow logic are documented in docs/user-guide.md.
If you are new to Zerf, start there for:
- first-login and first-week onboarding,
- role-based workflows,
- status and approval logic,
- flextime and leave-account behavior,
- practical answers for common edge cases.
The application is deliberately small in scope and operationally simple: a Rust backend, a Svelte frontend, PostgreSQL, and Docker-based deployment.
- Docker and Docker Compose on a Linux host.
opensslfor secret generation.- For public deployment: a domain pointing to the host and ports 80 and 443 reachable from the internet.
Copy the example file and fill in the required values:
cp .env.example .env && chmod 600 .env| Variable | Description |
|---|---|
ZERF_POSTGRES_USER |
Database user name |
ZERF_POSTGRES_DB |
Database name |
ZERF_SESSION_SECRET |
Random secret (≥ 32 chars) |
ZERF_POSTGRES_PASSWORD |
Database password |
ZERF_DB_ENCRYPTION_KEY |
Encryption key for database and backups — keep it safe |
ZERF_DOMAIN |
Public hostname (e.g. zerf.example.com) — public deployment only |
The three secret keys can be generated and written in one step:
sed -i "s|^ZERF_SESSION_SECRET=.*$|ZERF_SESSION_SECRET=$(openssl rand -hex 32)|" .env
sed -i "s|^ZERF_POSTGRES_PASSWORD=.*$|ZERF_POSTGRES_PASSWORD=$(openssl rand -hex 32)|" .env
sed -i "s|^ZERF_DB_ENCRYPTION_KEY=.*$|ZERF_DB_ENCRYPTION_KEY=$(openssl rand -hex 32)|" .envZERF_DB_ENCRYPTION_KEY protects data at two layers: the database is encrypted at rest, and every backup file is encrypted before being written to disk.
Keep this key safe. Losing it while the stack is stopped makes the database and all backups permanently unreadable. Store it in a password manager or secrets vault alongside your
.envfile.
Use the interactive restore script:
./scripts/restore.sh # pick from available backups
./scripts/restore.sh path/to/file.dump.enc # restore a specific fileThe script decrypts the backup, stops the app to prevent mid-restore writes, restores the data, and tells you when to restart. On restart the app automatically applies any pending schema migrations.
Migration compatibility:
- Backup older than current code → the app applies pending migrations on start (safe).
- Backup newer than current code → update the app binary before restarting.
| Mode | Command | Use case |
|---|---|---|
| Local | ./start_local.sh |
Personal or LAN use. The app is reachable at http://localhost:3333. No reverse proxy — HTTP only. |
| Public | ./start_public.sh |
Internet-facing deployment. Caddy handles HTTPS termination and serves the frontend. Requires a domain and a valid ZERF_PUBLIC_URL. |
On first launch, open the application in your browser. You will be prompted to create the initial administrator account with your email, name, and password.
Seeds a fresh (never-bootstrapped) database with realistic users, time entries, absences, and reopen-requests across all statuses — useful for evaluations and screencasts.
sudo apt install -y python3-psycopg2 python3-dotenv python3-argon2
python3 scripts/seed_test_data.py --yesReleases are published on the GitHub Releases page. Set ZERF_VERSION in your .env file to control which version runs:
| Value | Behaviour |
|---|---|
v1.2.0 (pinned) |
Runs that exact release — recommended for production |
latest |
Always follows the latest release |
dev |
Tracks the latest development build from main |
Then pull and restart:
docker compose -f docker/docker-compose-local.yml pull
docker compose -f docker/docker-compose-local.yml up -dOn restart the app automatically applies any pending database migrations.
