A CV builder that writes to DynamoDB, renders a PDF in memory, and stores it in S3 — deployed on AWS Elastic Beanstalk. It also tailors an existing CV to a specific job posting through the Gemini API.
Flask 3 · Python 3.11 · DynamoDB · S3 · Elastic Beanstalk · fpdf2
The Elastic Beanstalk environment was part of an AWS Academy lab account and has since been torn down, so the old public URL no longer resolves. Everything below runs locally against your own AWS resources using the setup at the bottom.
Build — a multi-section form (personal details, summary, education, experience, skills) that produces a formatted A4 PDF in one of two templates: Modern Tech with an indigo accent, or Minimal ATS in black and white.
Score — every saved CV gets an ATS score. This is a keyword heuristic, not a simulation of
any real applicant tracking system: the summary, skills and experience fields are lowercased
and matched against a fixed list of 24 industry terms, and the score is
min(100, found / 10 × 100). Ten matches is full marks. It is useful as a nudge to include
concrete technology names, and it is deliberately simple.
Tailor — paste a job description and the CV is rewritten to align with it. The prompt is explicit that the model may rephrase but may not invent:
Keep it truthful — don't invent skills or experience I don't have, but rephrase what I have to align with the job.
The rewritten CV is re-scored, so you can see whether the tailoring actually moved the number.
Dashboard — lists every CV with its score and a download link.
fpdf2, rendered into a memory buffer and uploaded straight to S3 — nothing touches local
disk, which matters on Elastic Beanstalk where the instance filesystem is ephemeral.
Font handling is the part worth reading. fpdf2's built-in fonts are Latin-1 only, which
breaks on Turkish and Polish characters, so the app loads a TrueType font instead — and which
font it can find depends on where it runs:
win_reg = 'C:/Windows/Fonts/calibri.ttf'
lin_reg = '/usr/share/fonts/liberation/LiberationSans-Regular.ttf'Calibri in development on Windows, Liberation Sans on Amazon Linux, and a fall back to core
fonts if neither exists. An earlier deploy failed because the EB config tried to install
msttcore-fonts, which is not available on Amazon Linux 2023; moving to the Liberation family
fixed it.
Called over plain urllib.request against the Generative Language REST endpoint rather than
through the SDK — one fewer dependency in the deployment bundle, and the request is a single
JSON POST.
The model returns JSON, but often wrapped in a markdown code fence, so the response is
unwrapped before parsing and a JSONDecodeError is caught rather than allowed to 500:
if response.startswith('```'):
response = response.split('```')[1]If GEMINI_API_KEY is unset the tailor endpoint degrades instead of crashing, and the rest of
the app keeps working.
| DynamoDB | One item per CV, cv_id as partition key. put_item on create, get_item / scan for the dashboard. |
| S3 | PDFs written with put_object. Downloads go through a pre-signed URL, so the bucket itself stays private. |
Elastic Beanstalk, Python 3.11 on Amazon Linux 2023, Gunicorn started from a one-line
Procfile. Configuration is passed as environment variables, and the IAM instance profile
carries the S3 and DynamoDB permissions so no credentials ship with the application.
| Route | Method | Purpose |
|---|---|---|
/ |
GET | Landing page with a live template preview |
/create |
GET, POST | CV form → DynamoDB + PDF to S3 |
/tailor |
GET, POST | Job description → Gemini rewrite → re-score |
/dashboard |
GET | All CVs, scores, download links |
git clone https://github.com/Tunaycel/cvforge
cd cvforge
pip install -r requirements.txtCreate a .env:
SECRET_KEY=<python -c "import secrets; print(secrets.token_hex(32))">
S3_BUCKET=<your bucket>
AWS_REGION=us-east-1
DYNAMO_TABLE=<your table>
GEMINI_API_KEY=<optional; the tailor page is disabled without it>
You need an S3 bucket and a DynamoDB table with cv_id as the partition key, plus credentials
in your environment or ~/.aws/credentials.
python app.pyeb init cvforge-app --platform python-3.11 --region us-east-1
eb create cvforge-env --single
eb setenv SECRET_KEY=... GEMINI_API_KEY=...
eb deploySet secrets with eb setenv, never in .ebextensions — anything in that directory is
committed to the repository.
- Delete is not implemented; a new submission overwrites the previous PDF for that CV.
- There is no authentication. The dashboard scans the whole table, so it is single-user by assumption rather than by enforcement.
scandoes not paginate, so it stops being the right call past a few hundred items.- The ATS score is a keyword count, and should be read as one.