Stop Applying Blind: JobOps Is Your DevOps Pipeline for Job Hunting
You're spending 40 hours a week on job applications and getting ghosted.
Sound familiar? You craft a generic resume, blast it across LinkedIn, Indeed, and Glassdoor, then manually track everything in a color-coded spreadsheet that makes your eyes bleed. Meanwhile, someone else—someone using JobOps—just applied to 50 tailored positions in the time it took you to write one cover letter. They're not cheating. They're not using sketchy automation that gets them blacklisted. They're simply applying DevOps principles to the most broken pipeline in their life: their job search.
Here's the brutal truth: the job market isn't fair. The people who win aren't always the most qualified. They're the ones who show up first, with the most relevant resume, and follow up flawlessly. JobOps, created by developer Shaheer Sarfaraz, is the open-source secret weapon that's already powered 4,000+ job searches for 800+ users and hit #3 on GitHub Trending for TypeScript. And unlike those creepy auto-application bots that recruiters can spot from a mile away, JobOps makes you faster without sacrificing the human touch that actually gets you hired.
Ready to stop treating your career like a side project? Let's dive in.
What Is JobOps?
JobOps is a self-hosted, open-source platform that brings infrastructure-as-code thinking to your job hunt. Built by Shaheer Sarfaraz and released under AGPLv3 + Commons Clause, it's explicitly not an auto-applier. Instead, it's an intelligent orchestration layer that sits between you and the chaos of modern recruiting.
The project exploded in popularity for one simple reason: it solves a problem every developer feels but nobody talks about. Job hunting is repetitive, demoralizing, and structurally inefficient. You search the same boards, rewrite the same resume bullets, and track the same statuses across a dozen different tools. JobOps collapses this entire workflow into a single dashboard with a pipeline architecture that would make any DevOps engineer nod in approval.
What makes JobOps genuinely different from the flood of "AI job tools" flooding Product Hunt? It respects the application process. Sarfaraz explicitly rejected auto-application functionality because recruiters can detect bots, and getting blacklisted is career suicide. Instead, JobOps gives you 10x speed on everything around applying—so you can focus your human energy on the actual conversations that land offers.
The project is built in TypeScript, containerized with Docker, and designed for developers who already understand pipelines, environment variables, and API keys. If you can deploy a microservice, you can deploy JobOps in under 10 minutes.
Key Features That Make JobOps Insane
Unified Multi-Board Search
JobOps scrapes 13+ job boards simultaneously including LinkedIn, Indeed, Glassdoor, Adzuna, Hiring Cafe, startup.jobs, Working Nomads, and niche platforms like Gradcracker (UK STEM), UK Visa Jobs (sponsorship tracking), Golang Jobs, Seek (Australia/NZ), WUZZUF (Egypt), and Khamsat (Egyptian freelance). No more tab hell. No more missing the perfect role because you forgot to check a board.
AI-Powered Fit Scoring
Every job gets a 0-100 relevance score against your profile. This isn't keyword matching—it's intelligent analysis of your skills, experience level, and preferences against the actual requirements. Stop wasting time on "purple squirrel" postings that want 10 years of Rust experience.
Dynamic CV Tailoring
Here's where the magic happens. JobOps rewrites your resume for each specific role, emphasizing relevant experience and matching terminology. The generated CV can export as a polished PDF locally or integrate with Reactive Resume for advanced formatting. This isn't mail-merge Mad Libs—it's context-aware rewriting that passes human scrutiny.
Gmail-Powered Application Tracking
Connect your email and JobOps automatically detects status changes from recruiter replies. Interview invitation? Status updates to "Interviewing." Rejection? Marked accordingly. No manual spreadsheet updates. No missed follow-ups. It's like having a CRM for your job search that actually reads your email.
Visa Sponsorship Intelligence
For international developers, this is massive. JobOps explicitly flags sponsorship availability—particularly critical for UK roles through the dedicated UK Visa Jobs integration. Stop getting three rounds into a process only to discover they won't sponsor.
Bring Your Own AI
JobOps doesn't lock you into a proprietary model. It supports Codex (local), OpenAI, Google Gemini, OpenRouter, and any OpenAI-compatible endpoint including Ollama and LM Studio. Run entirely offline if you're privacy-paranoid, or use the cheapest API for your volume.
Real-World Use Cases Where JobOps Dominates
The Visa-Seeking International Developer
You're a senior engineer targeting UK relocation. Without JobOps: you manually check LinkedIn, Indeed, and Glassdoor, then cross-reference the UK government's sponsorship register, then rewrite your CV to emphasize "right to work" adjacent skills. With JobOps: one search across all boards including UK Visa Jobs, automatic sponsorship flagging, and CVs that proactively address relocation experience. You go from 2 quality applications per day to 15.
The Niche Specialist (Go, Rust, Embedded)
Your skills are in demand but postings are scattered. Golang Jobs, niche Slack communities, company career pages—finding everything takes hours. JobOps's custom extractor system (written in TypeScript) lets you add any board with a consistent structure. Your specialized pipeline feeds you everything in one place.
The Career Pivoter
You're transitioning from backend to ML engineering. Every application needs a different emphasis—sometimes your Python data work, sometimes your distributed systems experience, sometimes your recent courses. JobOps generates role-optimized versions automatically, so you're not mentally context-switching between "infrastructure storyteller" and "model deployment expert" personas.
The Passive-but-Open Candidate
You're employed but would move for the right opportunity. Traditional job hunting demands too much time. JobOps runs your search pipeline in the background, surfaces only 90+ fit scores, and pre-writes your tailored materials. You spend 10 minutes reviewing, not 2 hours searching.
Step-by-Step Installation & Setup Guide
JobOps is designed for developers who already run Docker. If you've deployed a Compose stack before, this will feel familiar. If not, it's excellent practice.
Prerequisites
- Docker and Docker Compose installed
- Git
- An AI provider API key (OpenAI, Gemini, etc.) or local Codex setup
- Gmail account (for tracking features)
Installation
Clone the repository and start the stack:
# Clone the repository
git clone https://github.com/DaKheera47/job-ops.git
# Enter the project directory
cd job-ops
# Start all services in detached mode
docker compose up -d
This single command brings up the entire JobOps stack: the web application, database, and supporting services.
Initial Configuration
Open http://localhost:3005 in your browser. The onboarding wizard walks you through:
- Profile Setup: Import or create your base resume
- AI Provider Configuration: Add your API key or select local Codex
- Gmail Connection: OAuth authorization for automatic tracking
- Search Preferences: Roles, locations, salary ranges, remote flexibility
- Board Selection: Enable/disable specific job boards
You'll be searching in under 10 minutes.
Environment Customization
For production deployments or custom configurations, examine the docker-compose.yml and environment templates. Key variables include:
DATABASE_URL: PostgreSQL connection stringAI_PROVIDER/AI_API_KEY: Your chosen model configurationGMAIL_CLIENT_ID/GMAIL_CLIENT_SECRET: OAuth credentials for trackingUMAMI_DISABLED: Opt out of anonymous analytics
Updating
Pull latest changes and recreate:
git pull origin main
docker compose down
docker compose up -d --build
The project includes automated GHCR builds, so you're always getting tested images.
REAL Code Examples from the Repository
Let's examine how JobOps actually works under the hood, using patterns from the codebase and documentation.
1. The Core Docker Compose Stack
The entire deployment is defined declaratively—true to DevOps principles:
# docker-compose.yml - Simplified representation of JobOps architecture
version: '3.8'
services:
app:
image: ghcr.io/dakheera47/job-ops:latest
ports:
- "3005:3005" # Main application interface
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://jobops:jobops@db:5432/jobops
# AI provider configuration - flexible across providers
- AI_PROVIDER=openai # or 'codex', 'gemini', 'openrouter'
- AI_API_KEY=${OPENAI_API_KEY} # Injected from host environment
depends_on:
- db
- redis # Caching layer for scraped job data
volumes:
- ./data/resumes:/app/resumes # Persistent CV storage
- ./data/exports:/app/exports # Generated PDF output
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: jobops
POSTGRES_PASSWORD: jobops
POSTGRES_DB: jobops
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
What's happening here? This is a standard three-tier pattern: stateless application server, persistent database, and caching layer. The critical insight is environment-driven configuration—the same image runs with OpenAI, local Codex, or Ollama depending on injected variables. The volume mounts ensure your generated resumes and exports survive container restarts.
2. Custom Extractor Pattern
JobOps's extensibility comes from its TypeScript extractor system. Here's how you'd add a new job board:
// extractors/custom-board.ts - Pattern from official documentation
import { BaseExtractor, JobPosting } from '../extractors/base';
export class CustomBoardExtractor extends BaseExtractor {
// Unique identifier for this board
readonly name = 'custom-board';
// Base URL for all requests
readonly baseUrl = 'https://api.custom-board.com/v1';
async search(query: SearchQuery): Promise<JobPosting[]> {
// Construct search URL with encoded parameters
const url = new URL(`${this.baseUrl}/jobs`);
url.searchParams.set('q', query.keywords);
url.searchParams.set('location', query.location);
url.searchParams.set('remote', query.remote ? 'true' : 'false');
// Fetch with rate limiting and retry logic built into BaseExtractor
const response = await this.fetchWithRetry(url.toString());
const data = await response.json();
// Normalize to JobOps's internal schema
return data.jobs.map((raw: any) => this.normalize(raw));
}
private normalize(raw: any): JobPosting {
return {
id: raw.id,
title: raw.position_title,
company: raw.employer.name,
location: raw.location.display_name,
description: raw.description_html,
url: raw.apply_url,
salary: this.parseSalary(raw.compensation),
postedAt: new Date(raw.published_at),
// Critical: visa sponsorship flag for international job seekers
visaSponsorship: raw.tags?.includes('visa-sponsorship') || false,
// Source attribution for analytics and debugging
source: this.name,
};
}
private parseSalary(comp: any): SalaryRange | undefined {
if (!comp?.min && !comp?.max) return undefined;
return {
min: comp.min,
max: comp.max,
currency: comp.currency || 'USD',
period: comp.period || 'yearly',
};
}
}
Why this matters: The normalize() method is the contract. Any extractor, regardless of source format, produces a consistent JobPosting that the rest of the pipeline consumes. The visaSponsorship field demonstrates how domain-specific concerns (international mobility) are elevated to first-class data. Register this extractor in the orchestrator config, and it immediately participates in search, scoring, and tracking.
3. AI-Powered CV Tailoring Pipeline
The core value proposition—here's how JobOps transforms your base resume:
// services/cv-tailor.ts - Conceptual implementation from documentation patterns
import { OpenAI } from 'openai';
import { Resume, JobPosting } from '../types';
export class CVTailorService {
private ai: OpenAI;
constructor(apiKey: string, baseURL?: string) {
// Supports any OpenAI-compatible endpoint (Ollama, LM Studio, etc.)
this.ai = new OpenAI({ apiKey, baseURL });
}
async tailorResume(
baseResume: Resume,
job: JobPosting,
options: TailorOptions = {}
): Promise<TailoredResume> {
// Build structured prompt with explicit constraints
const systemPrompt = `You are an expert technical recruiter and resume writer.
Rewrite the provided resume to maximize relevance for the specific job description.
Rules:
- NEVER invent experience or skills the candidate doesn't have
- EMPHASIZE transferable experience using the job's terminology
- DE-EMPHASIZE irrelevant roles by reducing detail
- MAINTAIN all dates, companies, and verifiable facts exactly
- OUTPUT valid JSON matching the requested schema`;
const userPrompt = `BASE RESUME:\n${JSON.stringify(baseResume, null, 2)}\n\nTARGET JOB:\nTitle: ${job.title}\nCompany: ${job.company}\nDescription: ${job.description}\nRequired Skills: ${job.requiredSkills?.join(', ')}\n\nRewrite the resume to highlight the strongest fit.`;
// Structured generation with schema enforcement
const completion = await this.ai.chat.completions.create({
model: options.model || 'gpt-4-turbo-preview',
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt },
],
response_format: { type: 'json_object' }, // Enforce valid output
temperature: 0.3, // Low creativity = factual consistency
});
const tailored = JSON.parse(completion.choices[0].message.content!);
// Generate human-readable explanation of changes
const explanation = await this.generateExplanation(baseResume, tailored, job);
return {
...tailored,
metadata: {
originalId: baseResume.id,
jobId: job.id,
modelUsed: completion.model,
generatedAt: new Date(),
explanation, // Let user review what changed and why
},
};
}
private async generateExplanation(
before: Resume,
after: Resume,
job: JobPosting
): Promise<string> {
// Second pass: summarize changes for user review
const prompt = `Compare these two resume versions and explain in 3 bullet points what was emphasized for the ${job.title} role at ${job.company}. Be specific about which experiences were highlighted.`;
// ... OpenAI call returning human-readable diff
}
}
The critical safeguard: The systemPrompt explicitly forbids hallucination. JobOps doesn't invent skills—it reframes existing experience using the employer's vocabulary. The explanation field creates transparency: you review exactly what changed before sending. This is why recruiters can't detect automation—the underlying facts are real, just optimally presented.
Advanced Usage & Best Practices
Orchestrator Pipeline Tuning
JobOps includes a sophisticated orchestrator that sequences search → score → tailor → export → track. Advanced users can configure pipeline stages via environment variables:
ORCHESTRATOR_CONCURRENCY: Parallel job processing (default 3, increase for faster throughput)SCORE_THRESHOLD_MINIMUM: Only auto-tailor for jobs scoring above this (recommend 75+)AUTO_EXPORT_ENABLED: Generate PDFs immediately, or queue for batch review
Local AI for Maximum Privacy
Running Codex locally or Ollama with Llama 3 eliminates API costs and data exposure. Performance is slower but perfectly adequate for overnight batch processing. Configure in docker-compose.yml:
environment:
- AI_PROVIDER=openai
- AI_BASE_URL=http://ollama:11434/v1 # Local endpoint
- AI_MODEL=llama3:70b
Custom Scoring Weights
Override default fit scoring by creating .jobops/scoring-weights.json:
{
"skills_match": 0.35,
"experience_level": 0.25,
"location_preference": 0.20,
"company_size": 0.15,
"salary_range": 0.05
}
Backup Your Pipeline State
Your application history is valuable data. Schedule regular PostgreSQL dumps:
# Add to crontab for daily backups
0 2 * * * docker exec jobops-db pg_dump -U jobops jobops > ~/backups/jobops-$(date +\%Y\%m\%d).sql
Comparison with Alternatives
| Feature | JobOps | Huntr | Teal | LazyApply |
|---|---|---|---|---|
| Self-hosted | ✅ Free, full control | ❌ SaaS only | ❌ SaaS only | ❌ Desktop app |
| Auto-apply | ❌ Intentionally absent | ❌ | ❌ | ✅ (risky) |
| Multi-board search | ✅ 13+ boards | ⚠️ Limited | ⚠️ Limited | ⚠️ Limited |
| AI CV tailoring | ✅ Per-job rewriting | ❌ | ✅ Templates | ❌ |
| Gmail tracking | ✅ Automatic status | Manual | Manual | ❌ |
| Visa sponsorship | ✅ Dedicated integration | ❌ | ❌ | ❌ |
| AI provider choice | ✅ BYO or local | Locked | Locked | N/A |
| Price | Free (self-hosted) | $40/mo | $29/mo | $129 one-time |
| Open source | ✅ AGPLv3 | ❌ | ❌ | ❌ |
The verdict: Huntr and Teal are polished but expensive and opaque. LazyApply's auto-apply feature is exactly what gets candidates blacklisted. JobOps is the only tool that gives developers full control, respects the application process, and costs nothing to run. The trade-off? You need to docker compose up yourself.
FAQ
Does JobOps automatically apply to jobs for me?
No—and that's intentional. Auto-appliers create generic applications that recruiters detect instantly. JobOps automates everything except the final submit button: discovery, research, CV optimization, and tracking. You review and apply personally, which is exactly why it works.
Is JobOps really free? What's the catch?
Self-hosted JobOps is completely free under AGPLv3 + Commons Clause. The catch is you run it yourself. Cloud hosting (£20-30/month) funds development but is entirely optional. You cannot sell JobOps itself or offer competing hosted services.
Which AI provider should I use?
OpenAI GPT-4 gives the best tailoring quality. Google Gemini is cheaper with good results. Local models (Ollama, Codex) are free and private but slower. Start with OpenAI, migrate to local once you're confident in your pipeline.
Can I add job boards not in the default list?
Yes—this is a core feature. The TypeScript extractor system lets you add any board with consistent HTML or API structure. Documentation and community examples cover common patterns. Contribute back and help fellow job seekers.
How does Gmail tracking handle privacy?
JobOps uses OAuth2 read-only access to scan for recruiter emails. It never sends email, modifies your inbox, or accesses non-job-related messages. You can revoke access anytime. Self-hosting means your data never touches third-party servers.
Will recruiters know I'm using JobOps?
No. Your applications come from your email with your tailored resume. There's no watermark, no metadata, no telltale formatting. The CV tailoring uses your actual experience—it's optimization, not fabrication.
What if Docker isn't my thing?
The cloud option requires zero technical setup. Or follow the step-by-step self-hosting guide—it's excellent Docker practice even for beginners.
Conclusion: Your Job Search Deserves Infrastructure
You've built CI/CD pipelines for code deployment, monitoring stacks for uptime, and automated testing for quality. Why are you still job hunting like it's 2010?
JobOps brings the same systematic rigor to your career that you apply to your systems. It's not about cutting corners—it's about eliminating toil so you can focus on what actually matters: building relationships with the right companies and showing up prepared.
The numbers don't lie: 800+ users, 4,000+ searches, #3 on GitHub Trending. This isn't a side project anymore. It's the infrastructure layer job hunting has been missing.
Stop applying blind. Deploy your pipeline.
👉 Star JobOps on GitHub and get started with docker compose up -d today. Your future self—the one with multiple interviews lined up—will thank you.