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 five 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 injected into every pipeline run while the π§ Use memory toggle is on β a global preference that persists across chats, sessions, and reloads (stored server-side). Each generated reply also notes whether your master resume and/or profile memory were used, so it's clear later what fed the result.
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 mark one as the default β great for keeping a polished base and forking variants per role. Three ways to put them to work:
- β Use Master (input bar) attaches a master as the starting point for your next message, so you can tailor it to a JD or refine it through the pipeline.
- β New from Master (sidebar) opens a fresh chat with the master loaded verbatim β no AI changes. Click the main button for your default, or the caret to pick any saved master. Ideal for forking a polished base, or just attaching the application tracker to an untouched copy.
- Compare to Master on any resume version diffs it against a chosen master, so you can see exactly what tailoring changed relative to your base.
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. |
Five templates: Classic ATS (max ATS compatibility), Modern Clean (blue accents), Executive (dark slate), Dark Theme (deep navy), and LaTeX (serif, academic β Computer Modern with Font Awesome icons). 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.
