pocketwatch

pocketwatch

Docker app from winnicodes' Repository

Overview

Minimal self-hosted time tracking for freelancers. Start and stop a timer, edit entries, filter by client or period, and export a PDF report or CSV. Data lives in two JSON files in the mounted appdata folder - no database, no account, no telemetry, no outbound requests. The API has no authentication, so keep the container on your LAN or behind a reverse proxy that handles auth.

pocketwatch

Time tracking for freelancers and the self-employed. Start the timer with one click. Send the PDF to the client. All data stays on your machine.

Self-hosted Docker React 19 TypeScript No cloud License MIT

pocketwatch on the desktop: the running timer on the left, the history grouped by day on the right

🚀 Try it yourself

▶ Open the live demo

The demo is one HTML file. It contains the full app and demo data. You do not need a server, Docker or an internet connection. You can start, stop, search, filter, edit and export. The demo does not save data. If you reload the page, the demo starts again.


💡 Why pocketwatch?

🔒 Your data stays yours The app writes two JSON files to your disk. There is no account, no subscription and no tracking. No data leaves the machine. The container also contains the fonts.
⚡ Fast There is no database and no loading spinner. Start and stop are one click. The timer counts each second and continues after a reload.
🧾 Ready to invoice Make a PDF report with your name on it, or a CSV file for your spreadsheet. Export the filtered view or a date range.
📱 Desktop and phone The layout has two columns on a large screen. On a phone it shows one view at a time.
🐳 One container Use docker compose up -d --build. The container runs on a NAS, a Raspberry Pi or a laptop.

🎬 Functions

⏱️ Track

  • 📇 Client list, most recent first
  • ⏱️ Timer to the second
  • ✍️ Change the activity while the timer runs
  • 📊 Totals for today and this week
  • ⚠️ Notice after 8 hours
The Track view with the client list and the Clock In button

📜 History

  • 🔍 Search in the client and the activity
  • 📅 Grouped by day
  • 📌 The day heading stays at the top
  • ➕ Long text expands with more
  • Σ Total of the selection in the footer
The history filtered by a search for Gutenberg

🗓️ Period

Select Day, Week, Month or Year. Use the arrows to go to an earlier or a later period. The total changes with the period. Select All to show the full history.

The history filtered to the current week with the period selector

✏️ Edit

  • 📆 Start and end with date and time
  • ⏳ The duration updates immediately
  • ⌨️ Pickers that you can operate with the keyboard
  • 🚫 The app rejects an end before the start
  • ❓ Delete asks first
The Edit Entry dialog with date, time and duration

📤 Export

  • 📄 PDF report with your name and page numbers
  • 📊 CSV with semicolons and a BOM for Excel
  • 🎯 Current view, a standard period or a date range
  • 🔀 Newest first
  • ✂️ Times only, without activities
The Export dialog with the PDF and CSV cards and the period selector

⚙️ Settings

  • 👤 Name for the PDF
  • 🌐 German or English
  • 🕐 12-hour or 24-hour
  • 📌 Pin the day heading
  • ⚠️ Reminder for a long timer
  • 🔢 Round times to an interval from 1 to 60 minutes, up or to the nearest interval
Settings with rounding switched on and the interval control

📱 Responsive

Below 1024 px the app shows one view at a time. Use the drawer to change the view. Search, filter and export move into the header. Table rows become cards. Dialogs fill the screen.

The Track view on a phone
⏱️ Track
The history as cards
📜 History
The navigation drawer
☰ Drawer
The period filter on a phone
🗓️ Period
Edit on the full screen
✏️ Edit
Export on the full screen
📤 Export
Settings on a phone
⚙️ Settings
The app has a manifest, icons and a theme colour. Use “Add to Home Screen” to open pocketwatch without a browser bar.

🔧 Under the hood

  • The app stores the data in data/times.json and data/config.json.
  • Each write goes to a temporary file first. The app then renames the file.
  • Writes are debounced. No data is lost when you close the tab.
  • A running entry continues after a reload or on a different device.
  • The history loads in parts. The totals count all matches, not only the visible rows.
  • The container has Nginx and PHP-FPM on Alpine Linux.
  • The fonts are in the container. There is no CDN and no telemetry.
  • You can operate the app with the keyboard. Switches, lists and dialogs have ARIA roles.

🐳 Installation with Docker

The image is on the GitHub Container Registry. You do not have to compile it, and you do not need Node.js on the host:

docker run -d --name pocketwatch -p 8080:80 \
  -v "$(pwd)/data:/var/www/html/data" \
  ghcr.io/winnicodes/pocketwatch:latest

Open http://localhost:8080. The app writes the time entries to the mounted data/ folder.

Each release also has a version tag, for example ghcr.io/winnicodes/pocketwatch:1.1.0. Use a version tag if you want to control when you update.

Unraid

pocketwatch is in Community Applications. Open the Apps tab, search for pocketwatch and select Install. The template contains the WebUI link, the port and the appdata path. The default values are correct:

Setting Default
WebUI 8080 (host) → 80 (container)
Data /mnt/user/appdata/pocketwatch → /var/www/html/data

To update, use Docker → Check for Updates → Apply. You do not have to set PUID or PGID. The container corrects the owner of the appdata folder at start.

Add the container manually

Do this only if you do not use Community Applications. Select Docker → Add Container, then:

Field Value
Repository ghcr.io/winnicodes/pocketwatch:latest
Port 8080 (host) → 80 (container)
Path /mnt/user/appdata/pocketwatch (host) → /var/www/html/data (container)

The image is linux/amd64. Unraid uses this architecture.

Build the image yourself

The container compiles the frontend. You do not need Node.js on the host.

git clone https://github.com/winnicodes/pocketwatch.git
cd pocketwatch
docker compose up -d --build

The app writes the time entries to the data/ folder next to docker-compose.yaml.

Without docker-compose

Linux and macOS:

docker build -t pocketwatch-app .
docker run -d -p 8080:80 -v "$(pwd)/data:/var/www/html/data" --name pocketwatch pocketwatch-app

Windows (PowerShell):

docker build -t pocketwatch-app .
docker run -d -p 8080:80 -v "${PWD}/data:/var/www/html/data" --name pocketwatch pocketwatch-app

Docker does not accept a relative path such as ./data for -v. Use $(pwd) or ${PWD}.

The container corrects the permissions of data/ at start. A folder that root made is not a problem.

PowerShell helpers

The repository contains small wrappers for Windows: _docker-build.ps1, _docker-run.ps1, _docker-stop.ps1, _docker-restart.ps1, _docker-logs.ps1 and _docker-cleanup.ps1.


🛡️ Security and privacy

pocketwatch does not collect data, does not send data and does not load external files. There is no analytics, there are no fonts from Google and there is no external API.

Warning: the API has no authentication. Use pocketwatch in your own LAN only. Do not connect the container to the internet. A person who opens the URL can read and change all time entries. For access from outside, put a reverse proxy with authentication or a VPN in front of the container.


💾 Data

All data is in the mounted data/ folder:

  • data/times.json - the time entries
  • data/config.json - the settings (name, language, time format, rounding)

The app writes to a temporary file and then renames it. An interrupted write cannot damage a good file. If the folder is mounted on the host, the data stays after an update and after a rebuild of the container. _docker-cleanup.ps1 does not delete data/.

To make a backup, copy the data/ folder.


📁 Project layout

pocketwatch/
├── webapp/                 # React frontend (Vite)
│   ├── src/
│   ├── public/
│   │   ├── api/            # PHP API (read.php / write.php)
│   │   ├── fonts/          # Instrument Sans / Space Mono (in the repository)
│   │   └── locales/        # de.json / en.json
│   ├── scripts/            # demo-data.mjs, standalone.mjs
│   ├── dist/               # build output (not in Git)
│   └── package.json
│
├── data/                   # persistent data (not in Git)
│   ├── times.json          # time entries
│   └── config.json         # settings
│
├── docs/                   # published with GitHub Pages
│   ├── index.html          # single-file demo (npm run standalone)
│   └── *.png               # screenshots for this README
│
├── Dockerfile              # production container (Nginx + PHP + dist)
├── nginx.conf              # Nginx configuration
├── docker-compose.yaml     # the recommended way to start
└── README.md

🛠️ Local development

You need Node.js 20 or later.

cd webapp
npm ci
npm run dev

Vite serves http://localhost:5173 with hot reload. You do not need a container. Vite cannot run PHP, but vite.config.ts supplies api/read.php and api/write.php for development. It uses the same data/ folder as the container. In production, PHP is authoritative.

More scripts:

npm test         # tests for the time validation (node:test, no extra dependencies)
npm run build    # typecheck (tsc) and production build into webapp/dist
npm run demo-data -- --running   # demo data in data/ (--force overwrites, --en for English)
npm run standalone               # single-file demo (see above)

🔁 Updates

git pull
docker compose up -d --build

🚀 Tech stack

React 19 · TypeScript · Tailwind CSS 4 · Vite 7 · date-fns · react-day-picker · jsPDF + jsPDF-AutoTable · PHP 8.4 (FPM) · Nginx · Alpine Linux


❤️ Support

Please report problems and feature requests with GitHub Issues.

If pocketwatch saves you time, you can buy me a coffee: ko-fi.com/winnicodes. The same link is at the bottom left in the app.


📜 License

MIT - see LICENSE. You can use it and change it.

Media gallery

1 / 3

Install Pocketwatch on Unraid in a few clicks.

Find Pocketwatch in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.

Open the Apps tab on your Unraid server Search Community Apps for Pocketwatch Review the template variables and paths Click Install

Related apps

Details

Repository
ghcr.io/winnicodes/pocketwatch:latest
Last Updated2026-09-24
First Seen2026-08-10

Runtime arguments

Web UI
http://[IP]:[PORT:80]
Network
bridge
Shell
sh
Privileged
false

Template configuration

WebUIPorttcp

HTTP port for the WebUI.

Target
80
Default
8080
DataPathrw

times.json and config.json live here. Back this folder up.

Target
/var/www/html/data
Default
/mnt/user/appdata/pocketwatch