Jobify-JobSpy

Jobify-JobSpy

Docker app from Esg450's Repository

Overview

Job board search for Jobify (beta: results are scraped from the boards and can be incomplete). Runs JobSpy (https://github.com/speedyapply/JobSpy) as a small web service so Jobify's Find jobs page can search Indeed, LinkedIn, Glassdoor, ZipRecruiter, Google Jobs and other boards. Install Jobify first, then point Jobify at this container under Settings, Job search (or with the JOBSPY_URL variable). It has no web interface of its own and does not need a data folder.

Jobify

A self-hosted tracker for your job applications.

Jobify dashboard

Jobify keeps your whole job search in one place: the postings you are interested in, where each application stands, upcoming interviews and follow-ups, and your notes. It runs as a single Docker container with an embedded SQLite database, so there is nothing else to set up.

Board view Job details with timeline Dashboard in dark mode

Features

  • Track everything that matters. Title, company, location, remote/hybrid/on-site, employment type, salary range, source, contact, tags, notes, an interest rating and the full description.
  • Status pipeline. Saved → Applied → Screening → Interviewing → Offer → Accepted, plus Rejected, Withdrawn and Ghosted. Every status change is recorded on the job's timeline automatically.
  • List, board and timeline views. Search, filter and sort a table of jobs, drag cards between columns on a full-width Kanban board, or chart your search: a Gantt-style timeline of every application coloured by status with interviews marked, and a flow (Sankey) diagram of how applications moved between statuses. Both can be saved as PNG or SVG.
  • Timeline. Log interviews, notes and follow-ups against each job. Upcoming interviews, follow-up dates and deadlines show up on the dashboard.
  • Dashboard. Active applications, response rate, offers, your pipeline at a glance and applications per week. Drag the cards into the order you like and hide the ones you don't need; the layout is saved to your account.
  • Job hunts. Each search is its own hunt with its own list, board, charts and dashboard. Finish a hunt when you land a job and start a new one next time: you begin with a clean slate without deleting anything, and can look back at how every earlier hunt went (applications, response rate, interviews, offers and the job you accepted). Upgrading from an older version puts your existing jobs into a first hunt automatically.
  • Import from job sites. Paste a link and Jobify fills in the form. Dedicated importers for LinkedIn, Workday, Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Rippling and Oracle career sites, plus generic importers that read the structured data, embedded job data and page content that most other career pages have. Pages pasted from Indeed, Glassdoor and other sites that block automated access are recognised too, and plain text works as well.
  • Find jobs without leaving the app (beta). Connect the optional Jobify JobSpy container and search Indeed, LinkedIn, Glassdoor, ZipRecruiter, Google Jobs and other boards from a Find jobs page. Results that are already in your jobs are marked, and any other posting imports into your hunt with one click. Results are scraped from the boards, so expect the odd gap or stale posting.
  • Optional AI assistant. Connect Anthropic (Claude), OpenAI, Google Gemini, a local Ollama model or any OpenAI-compatible API to summarize postings, tidy up descriptions, draft cover letters, prepare for interviews and extract job details from any page.
  • Your data, portable. Export everything as JSON (and import it again) or as a CSV spreadsheet.
  • Multiple users. Everyone gets their own private jobs, timeline and profile. Admins add people or open sign-up, and manage the shared AI provider.
  • A dark mode, and a layout that works on phones.

Quick start

Docker Compose

services:
  jobify:
    image: ghcr.io/esg450/jobify:latest
    container_name: jobify
    restart: unless-stopped
    ports:
      - '3000:3000'
    volumes:
      - ./data:/data
    environment:
      PUID: 1000
      PGID: 1000
docker compose up -d

Then open http://localhost:3000. The first person to open Jobify creates the admin account.

Docker

docker run -d --name jobify \
  -p 3000:3000 \
  -v /path/to/jobify-data:/data \
  -e PUID=1000 -e PGID=1000 \
  ghcr.io/esg450/jobify:latest

Unraid

Jobify is in Community Applications; search for Jobify in the Apps tab. The optional Jobify JobSpy container for job board search is there too. If you prefer to install by hand, a template is included at unraid/jobify.xml. Copy it to /boot/config/plugins/dockerMan/templates-user/ on your server, then choose Add Container in the Docker tab and pick Jobify from the template list. Data is stored in /mnt/user/appdata/jobify and the container runs as 99:100 (nobody:users) by default.

Build from source

git clone https://github.com/esg450/jobify.git
cd jobify
docker build -t jobify .

Updating

Images are published to the GitHub Container Registry for amd64 and arm64:

Tag What it is
latest The newest release. Recommended.
1.2.3, 1.2 A specific release, if you want to pin a version.
beta The newest pre-release, for trying features before they ship.
edge The current main branch, for trying unreleased changes

On Unraid, new releases appear as update ready in the Docker tab; click it to update. With Compose, run docker compose pull && docker compose up -d. Your data is kept across updates and database migrations run automatically. The running version is shown on the Settings page.

The optional JobSpy container (ghcr.io/esg450/jobify-jobspy) uses the same tags but is only rebuilt by releases that change it, so it does not show an update every time Jobify does. The two can be updated independently: the connection between them does not change from release to release.

Configuration

All settings are optional environment variables.

Variable Default Description
PORT 3000 Port the web server listens on.
DATA_DIR /data Directory for the SQLite database.
PUID / PGID 1000 / 1000 User and group the container runs as. The data directory is handed to this user on startup.
LOG_LEVEL log How much to log: error, warn, log, or debug to also record every request.
AI_PROVIDER unset Default AI provider: anthropic, openai, gemini, ollama or openai_compatible.
AI_MODEL unset Default model. Anthropic defaults to claude-opus-5-5; other providers need a model name.
AI_API_KEY unset API key for the provider.
AI_BASE_URL unset Custom API endpoint, e.g. http://192.168.1.10:11434 for Ollama.
JOBSPY_URL unset URL of a Jobify JobSpy container, which turns on the Find jobs page. See Job search.
JOBSPY_API_KEY unset The key set on that container.

AI and job search settings can also be managed from the Settings page; settings saved there take precedence over the environment variables. Both pages let you test a connection before saving, and saving only succeeds once the provider or service answers.

AI providers

Provider What you need
Anthropic (Claude) An API key from the Anthropic Console.
OpenAI An API key and a model name.
Google Gemini An API key from Google AI Studio and a model name.
Ollama A running Ollama server and a model you have pulled. Point the base URL at it.
OpenAI-compatible API The API's base URL (for example OpenRouter, Groq, LM Studio or vLLM), a model and usually a key.

Add your resume and what you are looking for under Settings → Your profile to get cover letters and interview prep tailored to you. Job details and your profile are sent to the provider you choose, and nowhere else.

Importing jobs

Paste a posting URL on the Add job page. Jobify tries the importer for that site first, then works through the fallbacks: the schema.org JobPosting data most career sites publish, the markup of well-known job sites, job data embedded in the page's scripts, and finally the page's title and the block of text that reads most like a description. Whatever was found, a last pass reads the salary, location, remote/hybrid and employment type out of the text. Company careers pages that embed their postings from Ashby, Greenhouse, Lever or Workday are followed to the real posting. You always get to review the result before saving.

Some sites block automated requests or require you to sign in (Indeed, Glassdoor and LinkedIn when signed in, for example). In that case switch to Paste and paste the page source (right click → View Page Source → Select all → Copy, or from the browser's developer tools) or just the posting text. A copied posting usually comes through with its title, company, location and salary; with an AI provider configured, Jobify can fill in anything that is still missing.

Want to support another site? Importers are small, self-contained classes; see CONTRIBUTING.md.

Job search

Beta. Job board search is new. Results are scraped from the boards by JobSpy, so they can be incomplete, out of date or occasionally wrong (a salary read from the wrong line, a location that is really the company's HQ). Treat them as leads and check the posting before you apply.

Jobify can search the job boards for you through JobSpy, which it runs as a second, optional container (ghcr.io/esg450/jobify-jobspy, source in apps/jobspy). Keeping it separate keeps the scraping dependencies out of the main image and lets you update or restart it on its own.

  1. Start the container. On Unraid, install Jobify JobSpy from Community Applications, the same place you installed Jobify, and set an API key (if it is not in the store yet, use the unraid/jobify-jobspy.xml template the same way as Jobify's). With Compose, uncomment the jobspy service in docker-compose.yml. By hand:

    docker run -d --name jobify-jobspy -p 8000:8000 -e API_KEYS=change-me ghcr.io/esg450/jobify-jobspy:latest
    
  2. Connect it. In Jobify open Settings → Job search, enable it, enter the container's URL as seen from the Jobify server (for example http://192.168.1.10:8000) and the key, then Test connection. Saving checks the connection too, so a wrong address is never saved. A Find jobs page appears in the sidebar for everyone. Once connected, Search defaults on the same page sets how searches run: the boards selected by default, the country for Indeed and Glassdoor (they search one country at a time), the search radius, whether LinkedIn descriptions are fetched during the search or only on import, yearly salary figures, and proxies to spread searches across addresses.

  3. Search. Enter keywords, a location, which boards to search and how recent the postings should be. With an AI provider set up and something in your profile, Suggest from my profile fills in the keywords, location and remote preference for you (and offers a few alternative titles to try); nothing runs until you press Search, so you can adjust it first. Postings you already track are marked; Import on any other one runs it through Jobify's importers for the full description and opens the Add job page for you to review and save.

Searches scrape the boards live, so they take a while (up to a minute across several boards) and the boards rate-limit addresses that search too often. A board that answers with nothing is automatically tried once more a few seconds later; if LinkedIn or Glassdoor keep returning nothing, wait a bit, search fewer boards at a time, or add proxies under Search defaults (or on the container with DEFAULT_PROXIES). The service also works with the community jobspy-api container, which uses the same endpoints.

Users and accounts

The first person to open a new Jobify creates the admin account. After that, admins can add people under Settings → Users, or turn on sign-up so people can create their own accounts. Each user's jobs, timeline and profile are private to them. The AI provider is shared and only admins can change it.

Upgrading from a version without accounts? Your jobs are kept. On first launch, Jobify asks you to create the admin account and moves your existing jobs and profile into it. If you had set JOBIFY_PASSWORD, you'll need to enter it on that screen; this stops someone else from claiming your data first. You can remove the variable afterwards.

Forgot a password? Admins can set a new one for any user under Settings → Users. If you are locked out of the only admin account, reset it from the server:

docker exec jobify node apps/server/dist/cli.js list-users
docker exec jobify node apps/server/dist/cli.js reset-password <username>

Security

  • If Jobify is reachable from outside your home network, put it behind a reverse proxy with HTTPS and leave sign-up turned off unless you need it.
  • On a brand-new instance, create the admin account straight away: until you do, whoever opens Jobify first becomes the admin.
  • The server fetches the URLs you import. Anyone with an account can make it request web pages.
  • Passwords are hashed with scrypt, sessions are stored as hashes, and sign-in attempts are rate-limited. API keys are stored in the database in your data directory and are never sent to the browser.

Logs and troubleshooting

Jobify writes its log to logs/jobify.log in the data directory (on Unraid, /mnt/user/appdata/jobify/logs/), alongside the container's console output. Server errors, failed requests and errors from people's browsers all end up there. Admins can also download it under Settings → System. Set LOG_LEVEL=debug to record every request while you track down a problem. Log files roll over at 5 MB, and the three most recent old files are kept.

Backups

Everything lives in jobify.db in the data directory, so back up that directory to back up every user. Each user can also download their own jobs as JSON or CSV under Settings → Your data.

Development

Requirements: Node.js 24 and npm.

npm install
npm run dev        # API on :3000 and the web app on http://localhost:5173
Command What it does
npm run dev Run the API and web app with live reload
npm test Run the test suite
npm run lint / npm run format Lint / format the code
npm run typecheck Type-check both apps
npm run build Production build of both apps
npm run db:generate -w apps/server Create a migration after changing the schema
npm run seed -w apps/server Fill your account with demo jobs (-- --remove deletes them)

The stack is NestJS with Drizzle ORM on SQLite for the API, and React with Vite, TanStack Query and Tailwind CSS for the web app. See CONTRIBUTING.md for how the code is organised.

License

Jobify is free software released under the GNU Affero General Public License v3.0. You can use, self-host, modify and share it. If you distribute a modified version, or run one as a service for other people, you must make your source code available under the same license.

Requirements

Jobify. Install it first, then enter this container's address under Settings, Job search in Jobify.

Related apps

Details

Repository
ghcr.io/esg450/jobify-jobspy:latest
Last Updated2026-10-14
First Seen2026-10-14

Runtime arguments

Network
bridge
Shell
sh
Privileged
false

Template configuration

PortPorttcp

Port Jobify connects to. Use http://[unraid ip]:[this port] as the JobSpy URL in Jobify.

Target
8000
Default
8000
Value
8000
API keyVariable

A secret of your choosing. Enter the same value as the JobSpy API key in Jobify. Leave empty to allow anyone on your network to use the service.

Target
API_KEYS
ProxiesVariable

Optional, comma-separated proxies (user:pass@host:port) to route searches through. The job boards rate-limit repeated searches from one address.

Target
DEFAULT_PROXIES
Max resultsVariable

The most postings one search may request per board.

Target
MAX_RESULTS
Default
100
Value
100
Concurrent searchesVariable

How many searches run at the same time; others wait their turn.

Target
MAX_CONCURRENT_SEARCHES
Default
2
Value
2