Skip to content

Repository files navigation

Bert

Bert is a self-hosted job-search workspace for collecting vacancies, filtering and ranking results, tracking applications, and comparing jobs with candidate CVs.

It supports separate user workspaces, scheduled searches, profile-specific rules, notifications, and an administrator area for system management.

Features

  • Search Profiles and independently scheduled Search Jobs
  • Job Fit, Language Fit, Overall Fit, and evidence-based CV Match
  • German, English, mixed, and unknown job-ad language detection
  • Per-search preferred or strict working-time handling for full-time, part-time, Werkstudent, and Minijob searches
  • Bilingual role-first query planning, structural role relevance, and Strong / Match / Stretch result tiers
  • Candidate Profiles with encrypted CV text
  • Optional local Ollama context for CV analysis
  • Application workspace with Kanban/list views, follow-up dates, contacts, activity history, and source conversion
  • Manual vacancy capture with profile-aware Job Fit and Language Fit scoring
  • Explicit Markdown handoff of selected applications to career-ops
  • User-specific learning, notifications, searches, applications, and candidate data
  • Admin-managed registration, account activation, sessions, logs, backups, and updates
  • Responsive desktop, tablet, and mobile interface
  • Persistent SQLite storage in the Docker /data volume

Job sources

Stable integrations:

  • Arbeitnow
  • Adzuna
  • Jooble
  • Greenhouse
  • Lever
  • SmartRecruiters
  • RSS and Atom feeds

Experimental integrations:

  • JobSpy for LinkedIn, Indeed, Google Jobs, and Glassdoor
  • StepStone Germany
  • Kleinanzeigen Jobs

Experimental sources can be affected by rate limits, anti-bot controls, or upstream page changes. Bert does not bypass CAPTCHAs or other access controls.

Search matching

Bert treats provider search results as discovery candidates, not as confirmed matches. Each vacancy passes through separate stages so a high language, location, or skill score cannot rescue an unrelated occupation:

  1. Query planning removes location and working-time constraints from base role queries, distributes the first provider queries across requested role families, and adds English/German variants before narrower schedule variants. Custom Search Job queries remain isolated from the profile's other provider queries.
  2. Role relevance requires a requested phrase or role family in the title. A generic professional title such as Engineer II is accepted only when its description supplies both role-family and industrial-domain evidence. Conflicting software/data occupations are rejected unless supported by multiple manufacturing signals.
  3. Independent dimensions rank Job Fit, Language Fit, working-time fit, learned preferences, and optional CV Match.
  4. Result tiers expose Strong, Match, and Stretch vacancies in Job Review. Rows without structural role evidence—or rows that fail an explicitly strict eligibility rule—are marked excluded and are not shown, even when their old Overall Fit would have crossed the threshold.

The CV Match threshold becomes a hard gate only when the provider supplied a sufficiently complete description. Title-relevant vacancies with a short card/snippet remain visible as Stretch with a deferred-CV explanation instead of being rejected for evidence the source did not provide.

Search Jobs default to Prefer profile hours; keep stretch roles. This keeps a strong technical vacancy visible when it is full-time or its hours are missing, while labeling the constraint for review; it can still be notified when the other configured thresholds pass. Select Strictly exclude other/unknown hours when the working arrangement is a hard requirement; those vacancies are excluded from that Search Job's review queue and notifications. Student-only vacancies are always excluded unless the profile explicitly targets enrolled-student work.

During the database migration, existing profile scores are re-evaluated by the role gate. Run each Search Job once after updating to fetch fresh source data and populate the new match tiers. For a profile with four target role families, six provider queries cover every base role plus two translated variants; use eight when provider capacity permits to cover both English and German for all four families.

Quick start

Requirements:

  • Docker with Docker Compose
  • Port 8080, or a custom port mapping
  • A stable APP_SECRET_KEY

Clone the repository and prepare the environment:

git clone https://github.com/emnl51/bert.git
cd bert
cp .env.example .env
python3 -c "import secrets; print(secrets.token_urlsafe(48))"

Set at least these values in .env:

ADMIN_USERNAME=admin
ADMIN_PASSWORD=replace-with-a-long-unique-password
APP_SECRET_KEY=replace-with-the-generated-secret

Do not set APP_VERSION in .env. Release versions are embedded into the container image at build time; a runtime override can otherwise make the UI report a stale version.

Start Bert:

docker compose up -d --build

Open:

http://SERVER_IP:8080

Check the deployment:

docker compose ps
curl http://127.0.0.1:8080/health

Prebuilt GHCR image

The GHCR Compose file uses:

ghcr.io/whojan/bert

Run the latest image:

git clone https://github.com/emnl51/bert.git
cd bert
cp .env.example .env
docker compose -f docker-compose.ghcr.yml pull
docker compose -f docker-compose.ghcr.yml up -d

To pin a release, set BERT_IMAGE_TAG before running Compose:

BERT_IMAGE_TAG=<release-version> docker compose -f docker-compose.ghcr.yml up -d

Both deployment methods use the persistent bert_data volume.

Initial configuration

After signing in as the administrator:

  1. Create or review Search Profiles.
  2. Configure job sources under Sources → Source Catalog.
  3. Create Search Jobs and assign profiles, schedules, and optional Candidate Profiles.
  4. Configure personal Telegram or email notifications in the user workspace.
  5. Configure account-activation email under Administration → System Email.

The Search Profile editor keeps the everyday choices in Profile essentials: target role level, role families, working arrangements, German and English ability, preferred weekly hours, availability, and provider-query languages. Saving automatically adds generated bilingual suggestions while preserving existing custom terms. Raw thresholds, exact query lists, allowlists, blocklists, and scoring JSON remain available under the collapsed Advanced matching settings section.

Role level is an eligibility boundary rather than a score boost. A technician profile rejects engineering, management, and student-only titles; an engineering profile rejects technician-only and student titles; a working- student profile requires an explicit student-role signal. Weekly hours and availability act as labeled preferences for Search Jobs in preference mode and as exclusions in strict working-time mode.

Part-time and working-student profiles always require a confirmed work type after Bert inspects the job title, available description, hours/workload text, and provider metadata. A vacancy that still shows Work time unknown is excluded from review and notifications even when its Search Job uses preference mode.

Profiles referenced by Search Jobs cannot be deleted. Bert reports the linked Search Job names so they can be reassigned or removed first; profile-specific scores are deleted only after those references are resolved.

System email and job-notification email are separate. System email settings can be managed in the admin UI; the SYSTEM_SMTP_* values in .env remain available as fallback defaults.

Candidate Profiles and CV Match

Bert extracts job requirements and links each result to CV evidence using match, partial, or missing states.

The default hybrid score combines:

  • 70% deterministic evidence
  • 30% optional Ollama context

Ollama is disabled by default. When it is enabled, AI can add evidence-linked context but cannot change deterministic requirement states or invent CV evidence. If Ollama fails or times out, Bert falls back to deterministic scoring.

Configure it under Intelligence → Hybrid CV Match Engine. The default host endpoint is:

http://host.docker.internal:11434

The model must already exist in the configured Ollama installation.

Application workspace and career-ops handoff

Applications can be managed as a Kanban board or list. Each application stores its stage, application date, next action, due date, contact, notes, and an owner-scoped activity timeline. The workspace also shows overdue actions, stage totals, and source progression.

Use Add job to capture a public vacancy that was found outside Bert's providers. Bert scores the pasted vacancy against the selected Search Profile and adds it to To Apply. Review the extracted fields before saving and do not paste private correspondence or credentials into the public vacancy description.

The Export for career-ops action downloads a Markdown handoff containing the vacancy, Bert's match evidence, and the current application state. Nothing is sent automatically. Review the file before sharing it with an AI CLI or provider; CV tailoring, cover letters, and interview preparation remain explicit career-ops tasks.

Interface and themes

The workspace uses a responsive, minimalist layout with keyboard-visible focus states, touch-friendly controls, and high-contrast text and surfaces in both color modes. Open Settings → Interface to choose English or Turkish and select System, Light, or Dark. Preferences are stored only in the browser; System follows the operating-system color scheme.

The Fredy-inspired workspace uses five stable top-level destinations: Control Panel, Jobs, Job Review, Settings, and Administration. The neutral 220 px sidebar collapses to a centered 60 px icon rail. Jobs and Job Review keep compact workflow submenus, while Settings and Administration expose their pages as horizontal content tabs instead of repeating long lists in the sidebar. Navigation search, keyboard focus, mobile behavior, Light/Dark themes, restrained surfaces, and compact 8 px cards remain available. The shell mounts navigation only after both the sidebar and main workspace are available.

Job Review cards show only the role, company, location, Overall Fit, work arrangement, freshness, and review actions. Select a card—or focus it and press Enter—to open the complete detail view. The original job site opens only from that view, so scanning the queue does not unexpectedly leave Bert.

Updating

Preserve the existing .env, Docker volume, and especially APP_SECRET_KEY.

Source deployment:

cd ~/bert
git pull --ff-only origin main
docker compose up -d --build

GHCR deployment:

cd ~/bert
docker compose -f docker-compose.ghcr.yml pull
docker compose -f docker-compose.ghcr.yml up -d

Do not run docker compose down -v unless you intentionally want to delete persistent data.

The optional web updater performs guarded fast-forward updates through a restricted host service. Setup instructions are in deploy/README.md.

Data and security

  • Keep APP_SECRET_KEY stable; changing it can make encrypted credentials and CV data unreadable.
  • SMTP passwords and Candidate CV text are encrypted at rest.
  • System SMTP passwords are never returned through the API.
  • Registration links are single-use and time-limited.
  • User-owned records are isolated by account.
  • Destructive database operations create a SQLite snapshot under /data/backups.
  • Do not expose Bert publicly without HTTPS and appropriate network controls.
  • Never mount the Docker socket directly into the web container.
  • Keep .env, database files, and backups out of Git.

Development

Bert uses Python 3.12. The stable ASGI entry point is app.application:app.

Every pull request must review and update this README so user-visible behavior, setup steps, and operational guidance stay aligned with the code. Release-impacting changes must also be recorded under Unreleased in CHANGELOG.md.

When a GitHub Release is published, the Sync release changelog workflow converts its release notes into an exact-tag section in CHANGELOG.md and opens an automated pull request. The operation is idempotent, so rerunning the workflow will not create duplicate release entries.

Run the test suite:

python -m pytest --cov --cov-report=term-missing -q

Additional CI checks include Python compilation, Ruff lint/format validation, JavaScript syntax checks, Docker image builds, and CodeQL analysis.

See CHANGELOG.md for release history and LICENSE for the Apache 2.0 license.

About

🤘🏽Bert, Self-hosted job search automation and application tracker with multi-source discovery, fit scoring, learning, analytics and responsive UI.

Topics

Resources

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages