Job Application Tracker is a self-hosted, privacy-first job application and vacancy management platform. Built using Symfony 8.1, PHP 8.5, and FrankenPHP in worker mode, it is optimized to run efficiently on Docker hosts.
It helps job seekers track vacancies, record application audit histories, manage contact networks, prep for interviews with scratchpad notes, and visually track their job hunt pipeline.
- Interactive Chevron Pipeline Dashboard: Visual chevron progress tracker with real-time status counts and instant filtering.
- Vacancy Aggregate Management: Manual entry for vacancy details, salary ranges, currency codes, work modes (Remote/Hybrid/Onsite), and structured summary text blocks.
- Salary & Commute Threshold Warnings: Visual alert indicators when vacancy salary ranges fall below user profile preferences.
- Workflow State Machine & Audit History: Event-driven state transitions (
BOOKMARKED→APPLYING→APPLIED→INTERVIEWING→NEGOTIATING→ACCEPTED) with automatic transition audit logs and note taking. - Standalone Entities: Decoupled Company and Recruiter entities supporting multiple contact points (Name, Email, Phone, LinkedIn).
- Managed Tech Stack Tagging: Standardized technology tags for exact filtering and future AI matching.
- Flysystem Resume Storage: Local file abstraction layer for single active resume management (PDF/DOCX).
- Interview Scratchpad & Reminders: Markdown rich text notes per vacancy and target
next_action_atreminder dates.
- Phase 2 (Symfony AI): Automated vacancy text parsing, resume skills extraction, and AI-driven match scoring.
- Phase 3 (Integrations & Cloud): Distance matrix commute calculation, OpenGraph URL scraping, Google OAuth SSO, and AWS S3 storage adapters.
- Backend Framework: Symfony 8.1 / PHP 8.5
- Application Server: FrankenPHP 1.13.2 / PHP 8.5.12 (Worker Mode enabled)
- Database: MariaDB 13.0.2
- Frontend / Assets: Symfony UX 3.4 + AssetMapper (Zero Node.js build step required)
- Styling & Interactivity: Flowbite (Tailwind CSS) + Twig Components + Stimulus Controllers
- File Storage: Symfony Flysystem Bundle (
league/flysystem-bundle) - Messaging & Event Queue: Symfony Messenger (Outbox Pattern with Doctrine Transport)
- State Machine: Symfony Workflow Component
The pinned PHP 8.5.12 runtime includes upstream fixes for the critical remote code execution vulnerabilities CVE-2026-109618 and CVE-2026-109619. Development and production images built from the Dockerfiles use this patched runtime.
- Pragmatic Clean Architecture: Enforces strict hexagonal boundaries for external integrations (File Storage, Outbox Queue, AI Services) while maintaining a lightweight domain core using Doctrine ORM entities directly for standard CRUD operations.
- CQRS Lite: Clean separation between application commands (updates, status shifts, file uploads) and optimized repository read queries for dashboard rendering.
- Audit Logging over ES: Uses relational transition logs written via workflow listeners rather than full Event Sourcing.
- Docker & Docker Compose
- Docker host hardware with >= 1 GB available RAM
-
Clone and configure:
git clone https://github.com/curlysanders/job-application-tracker.git cd job-application-tracker cp .env.local.example .env.localSet a unique
APP_SECRETin.env.local. The checked-in database credentials are local-development defaults; an existing database volume needs its original credentials. MariaDB initialization variables apply only to a fresh volume. MariaDB is published only on127.0.0.1:3306; containers use the internaldbhostname, while local database tools can use the loopback endpoint. -
Build and install dependencies before starting workers:
docker compose build app docker compose run --rm --no-deps app composer install docker compose up -d
Composer also restores the pinned importmap dependencies. No Node.js or CSS compiler is required.
-
Validate the runtime and database:
docker compose exec app php bin/console about docker compose exec app php bin/console dbal:run-sql 'SELECT VERSION(), 1'
Apply the committed application migrations before using a fresh database:
docker compose exec app php bin/console doctrine:migrations:migrate --no-interaction -
Open the application at
https://localhost/. The component preview athttps://localhost/_componentsis available only indevandtest.
The development Compose override automatically restarts workers after changes to
application code, configuration, templates, and frontend assets. After changing
dependencies or Compose settings, run docker compose up -d --no-deps app.
Local development defaults to HTTPS. FrankenPHP/Caddy automatically redirects
http://localhost/ to HTTPS after the local CA is trusted.
The development stack serves https://localhost/ by default. FrankenPHP's
bundled Caddy instance creates a local certificate authority (CA) and an
automatically renewed certificate for localhost; it does not use a public CA,
DNS, or externally reachable ports.
-
Fresh installs receive this setting from
.env.local.example. Existing checkouts should set Symfony's URI in the untracked.env.localto:DEFAULT_URI=https://localhost
-
Start the development stack normally:
docker compose up -d
-
After the app is running, copy Caddy's root certificate from the container and add it to the trust store of the Docker host. Use the command for the host operating system:
Linux (Debian/Ubuntu and other distributions using
update-ca-certificates):docker compose cp app:/data/caddy/pki/authorities/local/root.crt /tmp/job-application-tracker-caddy-root.crt sudo install -m 0644 /tmp/job-application-tracker-caddy-root.crt /usr/local/share/ca-certificates/job-application-tracker-caddy-root.crt sudo update-ca-certificates
macOS:
docker compose cp app:/data/caddy/pki/authorities/local/root.crt /tmp/job-application-tracker-caddy-root.crt sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/job-application-tracker-caddy-root.crt
Windows (PowerShell):
docker compose cp app:/data/caddy/pki/authorities/local/root.crt "$env:TEMP\job-application-tracker-caddy-root.crt" certutil -addstore -f "ROOT" "$env:TEMP\job-application-tracker-caddy-root.crt"
Chrome normally uses the operating-system trust store; fully restart it after updating that store. If Chrome still shows a warning, open
chrome://settings/certificates, select the Authorities tab, choose Import, select/tmp/job-application-tracker-caddy-root.crt, and enable Trust this certificate for identifying websites. Restart Chrome after the import. Firefox configurations that do not use the operating-system trust store similarly require importing that sameroot.crtunder the browser's certificate-authority settings. -
Open
https://localhost/and confirm that the browser does not display a certificate warning.curl -I https://localhost/should also succeed after the root CA is trusted.
The root CA is retained in the caddy_data Docker volume. If that volume is
removed, Caddy creates a new CA on the next HTTPS startup and the trust step
must be repeated. Each additional device accessing the application also needs
this root CA installed; it is intentionally trusted only on devices you
administer.
HTTPS is the default because it better matches production and lets browser features that require a secure context work locally. To deliberately run plain HTTP, set the project-facing FrankenPHP override before creating the app container:
export FRANKENPHP_SERVER_NAME=:80
docker compose up -d --no-deps appIf Symfony needs to generate absolute URLs in that mode, also set
DEFAULT_URI=http://localhost in .env.local. To return to the HTTPS default,
unset FRANKENPHP_SERVER_NAME, restore the HTTPS URI, and recreate the app.
The older SERVER_NAME environment variable remains supported for existing
environments, but FRANKENPHP_SERVER_NAME is preferred for new local
configuration.
See TICK-101 validation and benchmark instructions.
Documentation has been structured under the docs/ folder:
docs/architecture.md— Architectural blueprint, pragmatic hexagonal patterns, and CQRS Lite guidelines.docs/workflow.md— GitHub workflow and CI/CD foundation.docs/development/roadmap.md— High-level phase vision and long-term features preview.docs/development/phase_1/epics.md— Phase 1 domain data model, aggregate entities, and epic definitions.docs/development/phase_1/tickets.md— Sprints 1–4 developer execution plan with actionable tickets and acceptance criteria.
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
AGPL-3.0 ensures the project remains free and open-source for personal use while requiring anyone who hosts a modified version as a public or commercial cloud service (SaaS) to publish their source code changes under the same license.