Self-hosted job discovery, filtering, ranking, scheduled search, application tracking and CV/job intelligence for configurable target locations.
Bert combines structured job APIs and ATS feeds with optional experimental job-board providers, profile-specific ranking, language and employment-format filtering, preference learning, notifications, analytics and a responsive admin UI.
- Responsive desktop, tablet and mobile admin UI
- Independent Search Profiles and scheduled Search Jobs
- Grouped Dashboard, Jobs, Intelligence, Settings and Administration navigation
- Full-page Job editor with explicit profile inheritance and per-job overrides
- Job Fit, Language Fit and Overall Fit scoring
- Strict employment-format filtering for Werkstudent / Part-time profiles
- Automatic German/English job-ad language detection with DE, EN, mixed and unknown filters
- Candidate Profiles with encrypted CV text
- Evidence-based CV Match with optional local Ollama AI context
- Profile-specific positive and negative preference learning
- Application workflow:
To Apply β Applied β Interview β Rejected / Offer - Telegram and email notifications
- Target-company ATS detection
- Source funnel analytics and quality metrics
- In-app log viewer with secret redaction
- Database backup, scoped reset and factory reset
- In-app release/commit tracking and guarded server updates through an optional host agent
- SQLite persistence in the Docker
/datavolume - Release images published to GitHub Container Registry (GHCR)
A Search Profile defines target location, search phrases, title/skill/format boosts, negative rules, minimum Job/Language fit, preferred German/English job-ad languages, employment-format expectations, and optional Candidate Profile assignment for intelligent filtering.
Fresh installations seed:
- Werkstudent / Part-time β strict student / part-time / Minijob matching
- Full-time Supply Chain β full-time Supply Chain / Operations / Procurement roles
For the strict part-time profile, a vacancy must contain a positive employment-format signal such as Werkstudent, Working Student, Teilzeit, Part-time, Minijob or compatible hours-per-week signals to pass the employment-format gate.
A Search Job is an independent automation built on a Search Profile. The profile supplies base location, search, scoring and filtering values. The Job editor can keep those inherited values or replace them with job-specific overrides.
Allowlist matches add positive Job Fit points but never force a vacancy into the results. Blacklist matches are hard exclusions: the vacancy is skipped before scoring, profile storage and notifications.
When a Candidate Profile is assigned, eligible jobs can automatically receive CV Match intelligence and are sorted using CV Match before normal fit scores.
- Arbeitnow
- Adzuna β credentials required
- Jooble REST API β API key required
- Greenhouse Job Board API
- Lever postings
- SmartRecruiters company postings
- Custom RSS / Atom feeds
JobSpy can retrieve jobs from LinkedIn, Indeed, Google Jobs and Glassdoor.
StepStone Germany is available as a separate experimental provider that reads normal public StepStone search-result pages. It is conservative by design:
- disabled until explicitly configured
- bounded search terms, pages and results
- configurable timeout and delay
- no CAPTCHA, proxy or anti-bot bypass
- HTTP 403/429 is reported instead of aggressively retried
- unparseable page layouts fail safely
- employment signals are preserved for the strict employment gate
A separate StepStone Germany β Manual search shortcut remains available as fallback.
Experimental providers can be affected by rate limits, anti-bot controls or upstream page changes. Stable API/ATS sources should remain the base of automated searches.
A vacancy passes through:
Provider result
β
Employment format
β
Job Fit
β
Language Fit
β
Overall Fit
β
Profile / learned rules
β
CV Match (when a Candidate Profile is assigned)
β
Recommendation / notification
Candidate Profiles store CV text, structured skills, languages and target roles. CV text is encrypted at rest using APP_SECRET_KEY.
Bert v16.4 uses a hybrid CV Match engine:
Final CV Match
= 70% evidence-based deterministic score
+ 30% optional Ollama contextual score
The deterministic score is split into seven categories:
| Category | Weight |
|---|---|
| Role / function | 20% |
| Relevant experience | 25% |
| Technical skills | 20% |
| Tools / software | 10% |
| Industry / domain | 10% |
| Education / certifications | 5% |
| Responsibilities similarity | 10% |
Each extracted requirement stores an evidence status:
match
partial
missing
and a supporting CV evidence excerpt. A missing requirement stays missing even when AI is enabled.
Ollama is disabled by default. With Ollama disabled, CV text is not sent to any LLM.
When enabled, Bert sends the Candidate Profile, job text and deterministic evidence table to the configured Ollama endpoint. Job description text is explicitly treated as untrusted data; instructions, tool calls and secrets are filtered before sending.
AI output can add contextual notes and transferable-experience explanations only when they reference an existing deterministic evidence ID. It cannot create a new matched requirement.
If Ollama fails or times out, Bert automatically falls back to the deterministic evidence score.
The Intelligence screen shows:
- Final CV Match
- Evidence score
- AI context score
- Seven-category score breakdown
- Strengths and gaps
- Risks
- Requirement-by-requirement evidence
- Transferable / partial evidence
- AI evidence-linked context notes
- Analysis engine
- Manual Re-analyze action
Analysis results are cached using Candidate Profile content, job content and AI settings. Unchanged jobs do not repeatedly call Ollama. Changing the CV, job description, model or AI configuration invalidates the cache.
Open Intelligence β Hybrid CV Match Engine.
Default endpoint:
http://host.docker.internal:11434
Both Docker Compose files map host.docker.internal to the Linux host gateway, so a host-installed Ollama instance can be reached from the Bert container.
Recommended starting configuration:
Enable Ollama: ON
Ollama URL: http://host.docker.internal:11434
Model: gemma3
Timeout: 60 seconds
The model must already exist in the configured Ollama installation.
Jobs can be reviewed as Suitable, Maybe or Not suitable. Positive and negative preference learning remains profile-specific.
Application stages:
To Apply β Applied β Interview β Rejected / Offer
Run History includes source-level funnel metrics:
Fetched β Unique β Job Fit β Language Fit β Recommended β New
The Logs screen captures application/Uvicorn logs in a bounded in-memory buffer with level/logger/text filtering, auto refresh, pause, TXT export and API key/token/password redaction.
The web buffer resets with the container; Docker stdout/stderr remains available through docker compose logs.
The Database screen supports scoped cleanup for jobs/applications, run history/analytics, learning and intelligence data, plus operational and factory resets.
A SQLite snapshot is created before destructive resets and stored under /data/backups in the persistent Docker volume.
Requirements:
- Docker with Docker Compose
- port
8080available, or adjust the mapping
git clone https://github.com/whojan/bert.git
cd bert
cp .env.example .env
python -c "import secrets; print(secrets.token_urlsafe(48))"Set strong values in .env:
ADMIN_USERNAME=admin
ADMIN_PASSWORD=<strong-password>
APP_SECRET_KEY=<generated-random-secret>
Start:
docker compose up -d --buildOpen:
http://SERVER_IP:8080
Open Sources β Source Catalog β StepStone Germany β Experimental β Configure.
Recommended initial configuration:
Maximum search terms: 3
Pages per search term: 1
Maximum results per term: 25
Request timeout: 30 seconds
Delay between requests: 1 second
Test before enabling if desired. Once enabled, StepStone can be selected by individual Search Jobs.
Published releases are pushed to:
ghcr.io/whojan/bert
Run a tagged image:
git clone https://github.com/whojan/bert.git
cd bert
cp .env.example .env
BERT_IMAGE_TAG=v16.5.0 docker compose -f docker-compose.ghcr.yml pull
BERT_IMAGE_TAG=v16.5.0 docker compose -f docker-compose.ghcr.yml up -dOr follow latest:
docker compose -f docker-compose.ghcr.yml pull
docker compose -f docker-compose.ghcr.yml up -dThe GHCR deployment uses the same .env, persistent bert_data volume and Ollama host-gateway mapping.
Keep the existing .env, Docker /data volume and especially APP_SECRET_KEY.
cd ~/bert
git pull origin main
docker compose up -d --buildDo not use docker compose down -v unless persistent volumes are intentionally being deleted.
Verify:
docker compose ps
docker compose exec bert python -c "import app.v16_main; print(app.v16_main.app.version)"Expected current main version:
16.7.0
The optional Updates screen compares the installed Git commit with the configured remote branch, shows deployment progress and recent updater logs, and can apply a safe fast-forward update.
Docker and Git privileges remain in a fixed-purpose host service; the web container never receives the Docker socket. The host agent creates a SQLite backup, rebuilds only the bert service and verifies the /health endpoint.
Python 3.12 is used by Docker and CI.
python -m pytest -q
# or
docker compose exec bert python -m pytest -qGitHub Actions checks:
python -m compileall -q app tests
node --check app/*.js
python -m pytest -q
The release-container workflow repeats these checks before publishing to GHCR.
The Docker entry point is:
app.v16_main:app
v10_main.py through v15_main.py are active composition layers, not obsolete upgrade artifacts.
.env, SQLite files and local backups are ignored by Git.- Keep
APP_SECRET_KEYstable; changing it can make encrypted credentials and Candidate CV data unreadable. - Ollama is optional and disabled by default.
- Job descriptions are treated as untrusted data in AI prompts.
- AI cannot promote unsupported requirements to matched evidence.
- JobSpy and StepStone are experimental; use conservative limits and timeouts.
- Do not expose the admin panel directly to the public internet without HTTPS and appropriate network controls.
- Bert now refuses to start while
ADMIN_PASSWORDorAPP_SECRET_KEYstill uses a published placeholder value. - For an internet-facing deployment, place the app behind an HTTPS reverse proxy.
Caddyfile.exampleincludes TLS, security headers and a rate-limit policy (the rate-limit directive requires a custom Caddy build).
Apache License 2.0. See LICENSE.