Self-hosted, AI-powered resume builder for software developers.
One sentence in → polished, ATS-optimised resume out — running entirely on your own machine.
StackResume.mp4
- What it does
- Why self-host?
- Quick start (Docker)
- Build from source
- Features
- Tech stack
- Architecture & pipeline
- Configuration
- Running without Docker
- Testing
- Contributing
- License
StackResume turns minimal input into a polished, interview-ready software-developer resume. The multi-agent pipeline:
- Parses your message into structured data, with optional persistent profile memory injected automatically.
- Generates a complete resume JSON with STAR-format bullets and quantified metrics.
- Reviews & enhances in a loop — a Reviewer agent scores ATS compatibility, writing quality, impact, and completeness (0–100 each), then an Enhancer rewrites weak bullets and injects missing keywords. Loops until the score crosses
MIN_QUALITY_SCORE(default 82) or hitsMAX_REVIEW_ITERATIONS(default 3). - Tailors — paste a job description to unlock a cover letter and a library of outreach email templates alongside the resume.
- Exports to PDF, Word (
.docx), or OpenDocument (.odt) across four visual templates.
Everything is wrapped in a chat-style web UI with persistent sessions, search, favourites, an application tracker, and a live LLM activity log.
StackResume is built to live on your infrastructure — a laptop, a homelab box, a private VPS, a corporate VM. Nothing is rented, nothing phones home.
- Your data stays put. Sessions, profile, master resumes, and uploads sit in a single SQLite file under the bind-mounted
./datadirectory. Back it up, encrypt it, move it between machines — it's a flat file. - No SaaS, no telemetry, no tracking. The container talks to one place: whichever LLM provider you configure. Nothing else leaves the box.
- Bring your own key — or skip keys entirely. Plug in OpenAI / Anthropic / Google Gemini, or point at a local Ollama install for fully offline, zero-cost inference. No vendor lock-in.
- Open source under GPL-3.0. Read it, audit it, fork it, patch it. The whole pipeline — agents, prompts, document renderers — is in this repo.
- One container, anywhere Docker runs. Multi-arch image (
amd64,arm64) — runs on a Raspberry Pi just as happily as on a bare-metal server. - Optional single-user auth. Flip one env var to put it behind SHA-512 + session-token login if you expose it on a LAN or behind a tunnel.
No clone needed — pull the pre-built multi-arch image (amd64 / arm64) straight from Docker Hub.
Minimal:
docker run -d -p 8000:8000 -v ./data:/data sathvikrao/stackresume:latestAPI keys and provider settings can be configured from the in-app ⚙️ Settings UI after the container is running.
Full docker-compose.yml (all options)
services:
stackresume:
image: sathvikrao/stackresume:latest
container_name: stackresume
ports:
- "8000:8000"
volumes:
- ./data:/data
environment:
# ── LLM Provider ────────────────────────────────────────────────────────
- LLM_PROVIDER=google # openai | anthropic | google | ollama | custom
- LLM_MODEL=gemini-2.5-flash
- LLM_TEMPERATURE=0.7 # 0 = deterministic, 1 = creative
# ── API Keys (set the one matching LLM_PROVIDER) ────────────────────────
- OPENAI_API_KEY=
- ANTHROPIC_API_KEY=
- GOOGLE_API_KEY=
- OPENAI_BASE_URL= # custom OpenAI-compatible endpoint only
# ── Ollama (local inference, no key needed) ──────────────────────────────
- OLLAMA_BASE_URL=http://host.docker.internal:11434
# ── Pipeline tuning ──────────────────────────────────────────────────────
- MAX_REVIEW_ITERATIONS=3 # max Reviewer→Enhancer loops
- MIN_QUALITY_SCORE=82.0 # stop early once score crosses this (0–100)
# ── Auth (off by default) ────────────────────────────────────────────────
- AUTH_ENABLED=false
- AUTH_USERNAME=admin
- AUTH_PASSWORD= # must be set when AUTH_ENABLED=true
# ── LangSmith tracing (optional) ─────────────────────────────────────────
- LANGSMITH_API_KEY=
- LANGSMITH_PROJECT=stackresume
- LANGSMITH_TRACING=false
# ── Misc ─────────────────────────────────────────────────────────────────
- CORS_ORIGINS=* # collapses to same-origin when AUTH_ENABLED=true
- DATABASE_URL=sqlite+aiosqlite:////data/resume_builder.db
- DEBUG=false
extra_hosts:
- "host.docker.internal:host-gateway" # needed for Ollama on the host
restart: unless-stopped
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/health')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20sdocker compose up -dOpen http://localhost:8000 once the container is running.
Prefer building the image yourself? Clone the repo, drop in a provider key, and let docker compose do the rest.
git clone https://github.com/Sathvik-Rao/StackResume.git
cd StackResume
cp .env.example .env# Google Gemini (default — fast and cheap)
LLM_PROVIDER=google
LLM_MODEL=gemini-2.5-flash
GOOGLE_API_KEY=AIza...
# OpenAI
# LLM_PROVIDER=openai
# LLM_MODEL=gpt-4o
# OPENAI_API_KEY=sk-...
# Anthropic Claude
# LLM_PROVIDER=anthropic
# LLM_MODEL=claude-sonnet-4-6
# ANTHROPIC_API_KEY=sk-ant-...
# Local Ollama — no API key needed
# LLM_PROVIDER=ollama
# LLM_MODEL=llama3.1Tip
No API key? Use LLM_PROVIDER=ollama with a local Ollama install — free, no internet required. Run ollama pull llama3.1 first.
docker compose up --buildOpen http://localhost:8000. The backend serves both the API and the frontend from the same port.
Tip
No Docker? See Running without Docker.
Nine specialised agents orchestrated with LangGraph — each resume goes through intent guarding, input parsing, optional JD analysis, generation, a Reviewer→Enhancer quality loop, and finalisation. If a job description is attached, a Cover Letter Writer and Outreach Writer run after finalisation.
| Agent | Role |
|---|---|
| Intent Guard | Blocks off-topic messages with a polite redirect — regex pre-filter first, LLM fallback only for ambiguous cases. |
| Input Parser | Extracts structured data from any free-form input. |
| JD Analyzer | (JD only) Pulls ATS keywords, required / preferred skills, and seniority from the job description. |
| Resume Generator | Produces the full JSON resume with STAR bullets and realistic fill for any missing fields. |
| Quality Reviewer | Scores ATS, writing quality, impact, and completeness (0–100 each). |
| Resume Enhancer | Rewrites weak bullets, injects missing keywords, fixes every issue the Reviewer flagged. |
| Finalizer | Stamps final metadata (iteration count, version, timestamp). |
| Cover Letter Writer | (JD only) Drafts a tailored cover letter. |
| Outreach Writer | (JD only) Drafts 2–4 templates — cold-application, LinkedIn, referral, follow-up. |
Click 🎯 Tailor to JD, paste any JD, and drag the intensity slider (0–100):
| Range | Effect |
|---|---|
| 100 | Full rewrite — every section mirrors the JD. |
| 65–89 | Heavy — summary + competencies tailored, bullets reordered. |
| 35–64 | Moderate — minor adjustments, a few JD keywords surfaced. |
| 10–34 | Light — 1–2 small tweaks. |
| 0–9 | None — JD used as background context only. |
Click 🧠 My Profile to store your identity, work history, projects, education, and target roles across five tabs. Profile data is automatically injected into every pipeline run.
Import / Export — download your profile as a portable JSON file and load it back on any install. Invalid files are rejected with a clear error.
Save any generated resume as a named master resume and set one as the default. The sidebar attach button starts a new chat with a specific master pre-loaded — great for keeping a polished base and forking variants per role.
Every resume has an ✏ Edit tab for inline editing of every section — summary, bullets, skills, education, etc. 💾 Save edits stamps metadata.manually_edited=true so subsequent agent runs preserve your changes. 🎯 Save & Re-score reruns only the Quality Reviewer — fresh scores in seconds without touching any content.
| Format | Notes |
|---|---|
| Universal, print-ready. Recommended for job portals. | |
Word (.docx) |
Editable in Word / Google Docs / Pages. |
OpenDocument (.odt) |
Editable in LibreOffice / OpenOffice. |
Four templates: Classic ATS (max ATS compatibility), Modern Clean (blue accents), Executive (dark slate), Dark Theme (deep navy). Font size (9 / 10 / 11pt) and page cap (auto / 1 / 2) configurable per export. The Export modal includes a live side-by-side PDF preview that updates as you change options.
Each session carries an optional tracker — status, apply URL, account email, masked password, and notes. Status pills appear in the sidebar so you can scan your whole pipeline at a glance.
Switch provider and model at any time from ⚙️ AI Model Settings. Supported: OpenAI, Anthropic, Google Gemini, Ollama (local), and any OpenAI-compatible custom endpoint.
Set AUTH_ENABLED=true to require login. Passwords are SHA-512 hashed client-side; the server compares with secrets.compare_digest and issues a UUID session token valid for 24 h.
Warning
You must also set AUTH_PASSWORD. If it's empty the server returns 503 Server auth misconfigured on every login attempt.
Key generation parameters are configurable via .env or the in-app settings UI:
| Parameter | Default | Effect |
|---|---|---|
LLM_TEMPERATURE |
0.7 |
Controls output creativity — 0 is fully deterministic, 1 is most creative. |
MAX_REVIEW_ITERATIONS |
3 |
How many Reviewer→Enhancer loops to allow before accepting the resume. |
MIN_QUALITY_SCORE |
82.0 |
Overall score threshold (0–100) at which the loop stops early. |
Set LANGSMITH_API_KEY + LANGSMITH_TRACING=true to stream every LLM call to your LangSmith project. A Test trace button in Settings verifies the connection end-to-end.
| Layer | Technology |
|---|---|
| Backend | FastAPI · LangGraph · LangChain · SQLAlchemy (async) · SQLite · aiosqlite |
| AI providers | OpenAI / Anthropic / Google Gemini / Ollama via LangChain |
| Document generation | ReportLab (PDF) · python-docx (Word) · odfpy (ODT) — pure Python, no headless browser |
| File parsing | pypdf + pdfminer.six · python-docx — for the resume upload flow |
| Frontend | Vanilla HTML / CSS / JS — single index.html, zero build step, zero npm |
| Deployment | Docker · docker-compose · GitHub Actions |
| Observability | LangSmith (optional) |
┌──────────────┐
│ Intent Guard │ ── off-topic ──▶ polite redirect ──▶ END
└──────┬───────┘
│ resume-related
▼
┌──────────────┐
│ Input Parser │
└──────┬───────┘
│ (jd_text present?)
┌───── yes ─┴─ no ─────┐
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ JD Analyzer │ ───────▶│ Resume Generator │
└──────────────┘ └────────┬─────────┘
▼
┌──────────────────┐
│ Quality Reviewer │◀─────────┐
└────────┬─────────┘ │
│ │
score ≥ MIN_QUALITY_SCORE │
or iter ≥ MAX_REVIEW_ITERATIONS no
│ │
yes │
▼ │
┌──────────────┐ ┌───────┴──────────┐
│ Finalizer │ │ Resume Enhancer │
└──────┬───────┘ └──────────────────┘
│ (jd_analysis present?)
┌──── yes ─────┴───── no ────┐
▼ ▼
┌──────────────────┐ END
│ Cover Letter │
└──────┬───────────┘
▼
┌──────────────────┐
│ Outreach Writer │
└──────┬───────────┘
▼
END
The graph is compiled once at startup (RESUME_GRAPH) and reused across requests. Background execution runs in a thread via loop.run_in_executor so LangChain's synchronous streaming doesn't block the FastAPI event loop. Cancellation goes through an in-process threading.Event registry keyed by message id.
| Variable | Default | Description |
|---|---|---|
LLM_PROVIDER |
google |
openai / anthropic / google / ollama / custom |
LLM_MODEL |
gemini-2.5-flash |
Model name for the selected provider |
LLM_TEMPERATURE |
0.7 |
0 = deterministic, 1 = creative |
OPENAI_API_KEY |
— | OpenAI key |
ANTHROPIC_API_KEY |
— | Anthropic key |
GOOGLE_API_KEY |
— | Google AI Studio key |
OPENAI_BASE_URL |
— | Custom OpenAI-compatible endpoint (LLM_PROVIDER=custom only) |
OLLAMA_BASE_URL |
http://localhost:11434 |
Use http://host.docker.internal:11434 inside Docker |
MAX_REVIEW_ITERATIONS |
3 |
Max Reviewer→Enhancer loops |
MIN_QUALITY_SCORE |
82.0 |
Stop refining once the resume reaches this score |
DATABASE_URL |
sqlite+aiosqlite:////data/resume_builder.db |
SQLAlchemy async URL |
AUTH_ENABLED |
false |
Require login on every /api/* route |
AUTH_USERNAME |
admin |
Admin username |
AUTH_PASSWORD |
— | Must be set when AUTH_ENABLED=true |
CORS_ORIGINS |
* |
Comma-separated origins. Collapses to same-origin automatically when AUTH_ENABLED=true and * |
LANGSMITH_API_KEY |
— | LangSmith tracing key |
LANGSMITH_PROJECT |
stackresume |
LangSmith project name |
LANGSMITH_TRACING |
false |
Enable LangSmith tracing |
DEBUG |
false |
FastAPI debug mode + SQL echo |
Settings flow through two layers:
- Baseline —
.env(or shell env) loaded at startup into thesettingsobject. - DB overlay — the
app_settingstable stores live edits from the 🔑 API Keys / ⚙️ AI Models modals. Non-empty values here override the baseline; clearing a field falls back to baseline.
runtime_settings.py also writes keys like OPENAI_API_KEY into os.environ so LangChain instrumentation and LangSmith pick up new values without a restart.
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
export LLM_PROVIDER=google
export LLM_MODEL=gemini-2.5-flash
export GOOGLE_API_KEY=AIza...
export DATABASE_URL="sqlite+aiosqlite:///./stackresume.db"
uvicorn app.main:app --reload --port 8000Open http://localhost:8000. For the frontend, either rely on FastAPI's static mount or open frontend/index.html directly with a local static server behind the same origin.
The pytest suite covers API routes, LangGraph agent nodes, document generators, and pure helpers. Every LLM call is swapped for a deterministic FakeLLM — the full suite runs offline with no provider keys.
cd backend
pip install -r requirements-dev.txt
pytest # everything (~30s)
pytest tests/unit # pure functions
pytest tests/api # HTTP-level via httpx.AsyncClient
pytest tests/agents # LangGraph node + pipeline
pytest tests/documents # PDF / DOCX / ODT smoke tests
pytest --cov=app --cov-report=term-missingIn Docker:
docker compose --profile test up --build --abort-on-container-exitSee backend/tests/README.md for fixture reference and contribution conventions.
See CONTRIBUTING.md for branch / test / PR conventions. Short version: open a PR against main, pytest must be green, and the CI bot will suggest which tests to add based on the changed files.
StackResume is free, open-source software released under the GNU General Public License v3.0 — see LICENSE for the full text.
You're free to run it, study it, modify it, and redistribute it. Derived works must remain GPL-3.0.
