JobOps: a self-hosted job search pipeline with a scraping layer and a rewrite step
job-ops: DevOps principles applied to job hunting. A self-hosted pipeline to track, analyze, and assist your application process
At a glance
- What is it?
- JobOps collects postings from more than ten job boards, scores each one against your profile, rewrites your CV per role, and tracks recruiter replies through Gmail. The interesting part is the orchestrator pipeline and the extractor interface. The licensing and the scrape targets are where you need to look carefully before adopting it.
- Who is it for?
- Adopt JobOps if you are running a broad search across several boards and want the scoring, CV rewriting and Gmail reply tracking in one self-hosted instance, and if you are comfortable with a TypeScript codebase you may need to patch when a board changes its markup. Do not adopt it if you need a stable, contractually supported tool, or if you intend to resell it or run it as a paid hosted service, because the AGPLv3 plus Commons Clause terms prohibit that.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem: job boards do not talk to each other, and your notes do not talk to your inbox
A job search at any volume produces three disconnected data sets. Postings live on LinkedIn, Indeed, Glassdoor and a long tail of regional boards. Your CV exists as one document that you edit by hand for each application. Your application status lives in your inbox, where a recruiter reply is the only signal that anything moved. JobOps targets all three. It scrapes more than ten boards from one screen, generates a rewritten CV per job description, and connects to Gmail to detect interview invitations, offers and rejections, updating the status automatically. The README states the tool does not auto-apply, on the grounds that recruiters can identify automated applications. That is a deliberate scope boundary: the project speeds up the parts around the application and leaves the submission itself to you. The intended user is a single job seeker or a small group self-hosting an instance, not a recruiting team and not an agency running applications at volume.
How the pipeline is structured: search, score, tailor, export, track
The README describes a five-stage flow. Search scrapes the configured boards for roles matching your criteria. Score ranks each result from 0 to 100 against your profile using an AI provider. Tailor generates a rewritten CV matched to the specific job description. Export produces a PDF locally or hands the document to Reactive Resume. Track connects to Gmail and watches for recruiter replies, mapping phrases such as an interview invitation to an Interviewing status and a rejection notice to Rejected. The repository also points to a separate Orchestrator Pipeline document, which suggests the stages are coordinated rather than run as independent scripts, but the README does not spell out the scheduling model, the queue, or how partial failures inside a run are handled. The extractor layer is the part with the clearest extension point: custom extractors can be added in TypeScript, and the project credits jobspy, a Python multi-source scraping library, as the engine behind the jobspy extractor. That means at least one code path crosses into Python, which matters if you plan to containerise or debug it.
Getting it running: three commands and an onboarding wizard
The README gives a three-command quick start. Clone the repository, change into the directory, and run docker compose up -d, then open http://localhost:3005 and follow the onboarding wizard. The README claims you will be searching in under ten minutes, which is a claim about the wizard, not a measurement of scrape latency. Images are published to GHCR, and the README links a self-hosting guide and a troubleshooting page for common problems. The AI provider is configured during onboarding rather than through a documented environment variable in the material supplied here. Supported options are Codex (described as a local app-server in Docker, authenticated with codex login), OpenAI, Claude, GLM or Zhipu AI, Google Gemini, OpenRouter, and any OpenAI-compatible endpoint such as Ollama or LM Studio. If you run a local model, the endpoint has to be reachable from inside the container, which is the usual failure point in a default Docker network. The README does not list the compose service names, volumes or environment keys, so plan to read the compose file before you change anything.
The scrape targets are the fragile surface, and the release notes show it
JobOps depends on extracting structured data from pages it does not control. The supported list includes LinkedIn, Indeed, Glassdoor, Adzuna, Hiring Cafe, startup.jobs, Working Nomads, Gradcracker, UK Visa Jobs, Golang Jobs, Seek (through Apify), WUZZUF and Khamsat. Every one of those is a separate parser with its own breakage schedule. The release history supports that reading: v0.12.1 is titled Hiring Cafe Fix, and v0.12.0 is titled OJCP, FreeHire and More Reliable Job Discovery. A patch release whose headline is a single board repair tells you where maintenance effort goes. This is the honest limitation of the project. If your search depends on one board and that board changes its markup, you are waiting for a release or writing the extractor yourself. The TypeScript extractor interface exists precisely for that case, and the README links extractor documentation, but the supplied material does not include the interface signature, so treat writing a new extractor as a real task rather than a configuration change. The Apify-backed Seek extractor also introduces a third-party dependency and, presumably, its own cost, which the README does not discuss.
Gmail tracking is the feature that saves the most time and grants the most access
Connecting Gmail lets JobOps detect status changes without manual entry. The README gives two examples: a message beginning with an invitation to interview moves the application to Interviewing, and a rejection message moves it to Rejected. The mechanism is described as watching for recruiter replies, which implies the tool reads message content rather than only metadata. That is a meaningful permission decision. A self-hosted instance means the mail content is processed on hardware you control, which is the argument for self-hosting in the first place, but it still means an application with broad Gmail access is running on that host. The README does not describe the scope requested, whether tokens are stored encrypted, or what happens to message bodies after classification. If you are not willing to grant that access, the tracking stage is simply unavailable to you and you fall back to updating statuses by hand, which removes a large part of the value. Nothing in the supplied material indicates a non-Gmail alternative.
Licence: AGPLv3 plus Commons Clause, and what that rules out
The README states the licence as AGPLv3 plus Commons Clause. Self-hosting, use and modification are permitted. Selling the software itself, or offering a paid hosted service whose value substantially comes from JobOps, is not. That restriction is the reason the project can also sell a cloud tier at 20 pounds per month for bring-your-own-key and 30 pounds per month with the AI provider included, both giving a dedicated instance and managed updates. The README says self-hosted will always be free and open source, and that hosted instances may enforce per-user monthly quotas on searches, AI tailoring, Ghostwriter generations and PDF exports, with self-hosted default mode unaffected unless quotas are explicitly enabled. If you were considering building a managed job-search product on top of this codebase, the Commons Clause is the clause that stops you, and it is worth reading the LICENSE file rather than the README summary. I am not a lawyer and this is not legal advice. For individual use the practical effect is nil.
Analytics are on by default, which is unusual for a self-hosted tool
The README states JobOps includes anonymous usage analytics through Umami, and that opting out means blocking umami.dakheera47.com at your firewall or DNS. There is no documented configuration flag to disable it in the supplied material. For a self-hosted application that handles your CV and reads your mail, an outbound analytics call enabled by default is a design choice worth flagging rather than glossing over. The opt-out is a network-level block, which works but is blunt: if you run the container on a host with unrestricted egress, the call happens until you intervene. Anyone deploying this on a personal server should decide about that block before the first run, not after.
How it compares to a spreadsheet plus a scraping script
The obvious alternative is the combination most job seekers already use: a spreadsheet for tracking and a small scraping script, or a tool such as jobspy directly, for discovery. JobOps is built partly on jobspy, so the difference is not the scraping. The difference is that JobOps bundles scoring, per-job CV rewriting, PDF export and inbox-driven status updates behind a dashboard, and ships them as a Docker image with an onboarding wizard. A spreadsheet and a script give you full control over what is collected and where it goes, with no analytics call and no third-party mail access, but you write the scoring and the CV tailoring yourself or do without them. There is also a category of hosted job trackers that require no setup at all. JobOps sits between the two: more assembled than a script, but requiring you to run and update a server. The cloud tier at 20 or 30 pounds per month is the project's own answer for people who want the assembled version without the server, and it is the same codebase with quotas layered on.
Maintenance cost and what to check before you commit
The release cadence visible in the material is roughly monthly for minor versions, with patch releases in between, and at least one patch dedicated to repairing a single board. That is the maintenance shape you are signing up for: periodic image updates, occasional breakage in one extractor, and the option to fix it yourself in TypeScript. The upgrade path is a container image pull, and the README does not describe database migrations or state that must survive an upgrade, so verify how the compose volumes are declared before you accumulate months of application history. Two other things to confirm on your own instance: whether the extractor for the board you care about most is currently returning results, since the release titles show these rotate, and whether your AI provider endpoint is reachable from the container network, because a local Ollama or LM Studio instance on the host is the most common misconfiguration. Neither is documented as a checklist in the material provided. If you want a tool you install once and forget, this is not it. If you want a pipeline you can read, extend and run on your own hardware, the extractor interface is the part that makes it worth the upkeep.
Editorial conclusion
Adopt JobOps if you are running a broad search across several boards and want the scoring, CV rewriting and Gmail reply tracking in one self-hosted instance, and if you are comfortable with a TypeScript codebase you may need to patch when a board changes its markup. Do not adopt it if you need a stable, contractually supported tool, or if you intend to resell it or run it as a paid hosted service, because the AGPLv3 plus Commons Clause terms prohibit that. Before you commit, verify three things: that the extractor for your primary board still returns results after the most recent release, that your chosen AI provider is reachable from the Docker network, and that the Gmail integration you intend to use matches the permissions you are willing to grant. The repository does not document its database schema or migration path, so check that yourself before you put real application history into it.
Community notes