A multi-user web app for tracking job applications through a pipeline — from initial interest through to offer and acceptance. Kanban board with drag-and-drop, table view, timeline view, file attachments, notes, salary tracking, and date tracking per stage. Each user sees only their own applications; admins can view all.
Requirements: Docker and Docker Compose, and a PocketID instance for authentication.
1. Clone and configure:
mkdir job-tracker && cd job-tracker
cp .env.example .envDownload the docker-compose.yml and .env.example files, or clone the repo.
2. Set up PocketID:
In your PocketID admin panel, create a new OIDC client:
- Redirect URI:
https://your-domain.com/oauth2/callback - Note the Client ID and Client Secret
3. Edit .env with your values:
OIDC_ISSUER_URL=https://your-pocketid-instance.example.com
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
PUBLIC_URL=https://your-domain.com
COOKIE_SECRET= # generate with: openssl rand -base64 32 | tr -- '+/' '-_'
LISTEN_PORT=30004. Update docker-compose.yml to use the pre-built image:
services:
job-tracker:
image: ghcr.io/fergus/job-tracker:latest5. Start:
docker compose up -dOpen your PUBLIC_URL in a browser. You'll be redirected to PocketID to log in.
Pull the latest image and restart:
docker compose pull
docker compose up -dYour data is safe — updates only replace the container, not the volume-mounted data directories.
All data is stored in Docker volumes mapped to local directories:
./data/— SQLite database./uploads/— uploaded attachment files
These directories are created automatically. Your data survives container restarts, rebuilds, and updates.
To back up:
cp -r data/ data-backup/
cp -r uploads/ uploads-backup/- Kanban board — drag cards between columns: Interested → Applied → Screening → Interview → Offer → Accepted/Rejected
- Table view — sortable columns, click any row for details
- Timeline view — visual history of status changes per application
- Hamburger menu — slide-in sidebar with the view switcher and account info; an "Always use menu" toggle (persisted per browser) controls whether the view switcher also appears inline in the header
- Settings panel — manage API keys; admins can toggle between personal and all-users view
- API keys — generate personal API keys for programmatic access without the browser OAuth flow; scoped to your account, shown once at creation
- File attachments — upload any file type (PDF, DOC, DOCX, etc. up to 10MB) as generic attachments
- Salary tracking — min/max salary range and job location per application
- Date tracking — timestamps auto-set when you move applications between stages; all dates are editable
- Stage notes — per-stage timestamped notes with markdown rendering and colored stage badges
- Links — store job posting and company website URLs
- Multi-user — each user sees only their own applications, identified via PocketID
X-Forwarded-Emailheader. Admins (configured viaADMIN_EMAILS) can view all users' applications but cannot edit or delete others' data
The server runs on port 3000 by default. To change the exposed port, set LISTEN_PORT in your .env:
LISTEN_PORT=8080To run without HTTPS (e.g. local dev), set:
COOKIE_SECURE=falseTo grant admin access (view all users' applications), set a comma-separated list of email addresses:
ADMIN_EMAILS=admin@example.com,boss@example.comgraph LR
Browser -->|HTTPS| Proxy["oauth2-proxy"]
Proxy -->|"X-Forwarded-Email :3000"| API["Express API"]
API <--> DB["SQLite (WAL)"]
API <--> FS["/app/uploads/"]
- Frontend (
client/): Vue 3 SPA, Vite, Tailwind CSS 4. State inApp.vue, API calls inclient/src/api.js - Backend (
server/): Express 5, better-sqlite3. Routes inserver/routes/ - Auth (
server/middleware/auth.js): oauth2-proxy headers (browser) or Bearer API key (programmatic) - Database (
server/db.js): 5 tables (users,applications,stage_notes,attachments,api_keys) +_migrationstracking
- Vue 3 + Vite + Tailwind CSS (frontend)
- Node.js + Express (backend)
- SQLite via better-sqlite3 (database)
- Single Docker container (multi-stage build)