Welcome to your new blog.
This site is intentionally simple: write posts as Markdown in the content/ folder, build once, and serve the pre-rendered HTML. No database, no JS framework, no fuss.
How it works
- Each post is a
.mdfile with YAML front matter at the top. - A build script parses your posts and generates a static site in
site/. - A tiny FastAPI app serves the generated HTML and a stylesheet.
- Optional Basic Auth keeps everything private if you want.
Write a post
Create a new file like content/my-first-post.md:
---
title: My First Post
date: 2025-01-02
tags: [notes, example]
excerpt: Short summary used on the homepage and RSS.
---
This is my first post. Markdown works as expected:
- Lists
- Headings
- Links like [FastAPI](https://fastapi.tiangolo.com/)
Inline code: `print("hello")`
Indented code block:
def hello(name: str) -> str:
return f"hello, {name}"
Build the site
From the project root:
python generate.py
This creates the static output in site/. Re-run this command whenever you add or edit posts.
Run locally
uvicorn app.main:app --reload --port 8080
Open http://localhost:8080
Private mode (optional)
Set environment variables to require Basic Auth for the entire site:
PRIVATE=true
BASIC_USER=me
BASIC_PASS=secret
Deploy
Containerize and deploy to Cloud Run:
docker build -t myblog .
docker run -p 8080:8080 -e BLOG_URL="http://localhost:8080" myblog
Then push and deploy on GCP (replace PROJECT_ID and REGION):
gcloud builds submit --tag gcr.io/PROJECT_ID/myblog
gcloud run deploy myblog --image gcr.io/PROJECT_ID/myblog --region REGION --platform managed --allow-unauthenticated
To keep it private on Cloud Run, add env vars:
--set-env-vars PRIVATE=true,BASIC_USER=me,BASIC_PASS=secret
Tips
- Set
BLOG_URLto your public domain so RSS and sitemap links are correct. - Use
tagsto group related posts. Tag pages are generated automatically. - Drafts: add
draft: trueto a post’s front matter to exclude it from builds.
That’s it—old-school, minimal, and fast. Happy writing.