My résumé, written in LaTeX and compiled in a reproducible Docker image — a clean single-page PDF that doubles as a small software project: versioned, built in CI, and layered with metadata that parsers, crawlers, and screen readers can read.
It started with a PDF viewer showing "resume" instead of my name in its title bar. That small annoyance turned into a question — what is a PDF, what metadata can it carry, and how much of a résumé can a machine actually understand? The answer became this repo: a résumé treated like a production system rather than a Word file.
📝 Full write-up — Making my resume machine-readable.
Optional, for verifying the output:
exiftool— inspect embedded XMP metadatapdftotext(poppler) — confirm ATS text extractionpython3+pypdf— read embedded attachments
Note
Compilation runs inside a custom Docker image for consistency and
reproducibility across environments. The image is amd64-only (pinned in the
Dockerfile), so pass --platform=linux/amd64 on Apple Silicon and other
arm64 hosts.
1. Clone the repository
git clone https://github.com/anishshobithps/resume.git
cd resume2. Build the Docker image
docker build -t latex-builder .docker3. Compile the resume
docker run --rm --platform=linux/amd64 -v "$(pwd):/data" \
latex-builder -jobname="Anish_Shobith_P_S_Resume" main.texNote
-jobname controls the output filename — change it as you wish. Run the
command twice if you add cross-references or metadata that need a second
pass to settle (the CI compiles twice for this reason).
The PDF is engineered so automated systems read it as cleanly as a person does.
The header displays short vanity URLs (n10nce.dev/github, n10nce.dev/linkedin)
but tags each one with a PDF /ActualText entry through the \atslink macro
(backed by the accsupp package). Text extractors, copy-paste, screen readers,
and ATS parsers pull out the canonical github.com/… and linkedin.com/in/…
strings — so profile-detection works — while the printed page stays branded and
tidy. The link's href still points at the n10nce.dev redirect, keeping any
click analytics intact.
Verify it:
pdftotext Anish_Shobith_P_S_Resume.pdf - | grep -iE 'github|linkedin'
# prints the canonical github.com / linkedin.com URLs, not n10nce.devformatting.sty enables Unicode glyph mapping (glyphtounicode,
\pdfgentounicode) and marks the document as tagged, so extracted text is
accurate and screen-reader friendly rather than a jumble of ligatures.
The compiled PDF carries rich metadata across several standards, readable by ATS platforms, semantic crawlers, and document parsers:
| Standard | Source | Description |
|---|---|---|
| XMP / Dublin Core | \hypersetup in main.tex |
Title, author, keywords, rights, language, dates |
| IPTC Core | \hypersetup in main.tex |
Contact email, URL, address |
| Schema.org JSON-LD | schema.json (embedded) |
Person, occupation, education, projects, skills with ATS aliases |
| JSON Resume | resume.json (embedded) |
Open standard parsed by ATS such as Workday, Greenhouse, Lever |
Note
Two traps the write-up documents (and this repo works around):
\hypersetup must stay in main.tex — hyperxmp emits no XMP
if it runs from a .sty file. And avoid LaTeX commands inside metadata values
(\DTMtoday, \textendash, …); they silently corrupt the XMP block instead
of erroring. Attachments use embedfile (spec-compliant name tree), not
attachfile2, whose annotations parsers can't see.
Inspect the XMP metadata:
exiftool -xmp:all Anish_Shobith_P_S_Resume.pdfExtract the embedded JSON attachments:
python3 -c "
import pypdf
r = pypdf.PdfReader('Anish_Shobith_P_S_Resume.pdf')
for name, data in r.attachments.items():
print(f'--- {name} ---')
print(data[0].decode('utf-8'))
"- Content — edit the files in
sections/; adjust PDF metadata inmain.tex. - Formatting — change layout, fonts, and macros in
formatting.sty. - Links — point
sections/header.texat your own handles;\atslink{href}{visible}{extracted}sets the displayed text and the canonical string ATS should read. - Structured data — update
schema.jsonandresume.jsonto reflect your own information.
Important
GitHub Actions rebuilds the image and publishes a new release on every push to
main that touches the sources (and on manual dispatch).
Download the latest compiled PDF from the Releases page.
Licensed under the Apache-2.0 License. See LICENSE for details.