Build a portfolio and host your CV
By the end of this guide you will have a portfolio timeline at /portfolio/, story pages that cross-link your work by tag, and a CV at /cv/ that prints to a clean one-pager and ships a machine-readable cv.json. All from markdown files, all checked by the verifier, all on your own domain.
It builds on your first site; any Cherry site works as the starting point.
How the pieces fit
You write entries into five collections. Cherry renders three surfaces from them, and the cv: block in each entry decides what the third one shows:
1 · The profile
The portfolio's header comes from one file, portfolio.yaml, at the site root:
name: Juno Vale
headline: Grows orchards and software.
location: Belfast
links:
- label: GitHub
url: https://github.com/junovale
- label: Fediverse
url: https://fedi.example/@juno
updated: 2026-08-01
2 · Your first position
Every collection has a schema, and the schema is the contract. Ask before you write:
$ cherry schema portfolio/positions
portfolio/positions frontmatter:
title: string (required) — Role title, e.g. "Staff Engineer".
org: string (required) — Organisation name.
start: date (required) — Start date.
end: date — End date; omit while the position is current.
location: string — City / remote — free text.
tags: list of string [default: []] — Tags from the shared site taxonomy (cross-linked with blog posts).
highlights: list of string [default: []] — Short bullet points for the timeline entry.
cv: cv block — CV curation: `{include, weight, highlights}`. Absent → timeline-only.
...
Then write content/portfolio/positions/orchard-systems.md:
---
title: Staff Engineer
org: Orchard Systems
start: 2020-02-01
location: Remote
tags:
- elixir
highlights:
- Grew the platform from seed to fruit
- Led a team of five gardeners
cv:
include: true
weight: 10
highlights:
- Grew the platform from seed to fruit
---
The long-form story of the orchard years, in markdown. This body
renders on the portfolio page; the frontmatter feeds everything else.
Omit end: while the role is current and the timeline says "present". Note the two highlight lists: the top-level one is for the timeline, the one inside cv: is the tighter cut for employers. More on that in step 5.
3 · Projects and talks, scaffolded
The generators write valid frontmatter so you do not have to remember it:
$ cherry gen.project "Cider Press" --json
{
"ok": true,
"command": "gen.project",
"data": {
"path": "content/portfolio/projects/cider-press.md",
"slug": "cider-press"
}
}
$ cherry gen.talk "Backpressure in practice" --json
{
"ok": true,
"command": "gen.talk",
"data": {
"path": "content/portfolio/talks/backpressure-in-practice.md",
"slug": "backpressure-in-practice"
}
}
Fill in what the scaffold left empty. Projects carry status: (active, paused, archived) and links:; talks carry event:, date:, and optionally video: and slides:. A talk with a recording can embed it right in the body with a video component, facade and all:
::video{youtube="q6Yr9DkTn2k" title="Backpressure in practice"}
The oss/ collection (title, repo, role: author, maintainer, or contributor) and education/ (title, institution, dates) round out the story. The verifier's unfilled-field rule nags about any scaffold string you forgot to replace, which is exactly the nag you want before an employer reads the page.
4 · The timeline and the story pages
Build, and two surfaces exist already:
/portfolio/interleaves everything by date under your profile header: positions as ranges, projects with status, talks and education as points in time./story/TAG/exists for every tag your portfolio shares with your blog. Tag a positionelixirand a postelixir, and the story page shows the job and the writing side by side. One taxonomy across the whole site, which is the part hand-rolled portfolios always lose.
5 · Curate the CV
The timeline is your story for peers; the CV is the cut for employers. The cv: block is the whole curation model:
include: trueopts an entry in. No block means timeline-only.weight:orders entries within their section, heaviest first.highlights:insidecv:overrides the timeline bullets with a tighter set.
/cv/ renders those entries dense and linear: skills backed by years, positions with curated bullets, projects, education. Nothing on it that you did not deliberately include.
6 · Print it, and ship the JSON twin
Open /cv/ and print. The stylesheet strips navigation, forces the light rendition (even from a dark screen, syntax colours included), and lays the page out for A4. That is the one-pager you attach to an application, generated from the same files as everything else.
Beside it, every build emits /cv.json in JSON Resume format, so the machine-readable version of your CV is never out of date with the human one.
7 · Verify like always
$ cherry check --strict
Checked 34 page(s): all clear.
The same rules cover the portfolio: broken links in entry bodies, empty scaffold fields, duplicate titles. Push, and the deploy workflow puts your story on your domain.
Keep the CV honest
Treat cv.highlights as claims you can defend in an interview and the timeline as the evidence behind them. The two-layer design exists so you never pad one to serve the other.