How to add a post
Posts live in the Oddy repository as Markdown files. There is no CMS and nothing runs on the server: the site image builds the blog with Zola and nginx serves the files.
Write the file
Create blog/content/<slug>.md. The file name is the address, so this post, blog/content/how-to-add-a-post.md, is at
https://blog.oddy.dev/how-to-add-a-post/. Start the file with TOML front matter between +++ lines:
+++
title = "How to add a post"
date = 2026-11-07
description = "One or two sentences for the blog's index and search results."
draft = true
[taxonomies]
tags = ["blog"]
+++
title: the heading and the browser tab.date: the publication date. The index lists posts newest first, and the feed only carries posts with a date.description: the summary under the title on the index, and the page description search engines show.draft:truekeeps the post out of the build until you set it tofalseor remove the line.tags: optional. Every tag gets its own page under/tags/.
Everything after the second +++ is the post, in Markdown.
Images
Give a post with images its own folder: blog/content/<slug>/index.md, with the images next to it, and link them by
file name: . Images from other sites do not load: the blog's Content Security
Policy only allows its own host.
Link to another post by its file, [the first post](@/how-to-add-a-post.md). The build fails when that file does not
exist, so links between posts never break.
Preview it
nix-shell blog/shell.nix --run blog/preview.sh
This builds the blog with drafts the way the site image does and serves it at http://127.0.0.1:1111. Run it again
after a change. The cookie banner and analytics stay off: they only work on Oddy's own hosts.
Publish it
Merge the post to main. A change under blog/ starts the deploy workflow, which builds the site image with the blog
in it and rolls it out. The post is on the index, in the feed at /atom.xml and in the sitemap.