Building a Seamless Writing Pipeline with Hedgedoc, Jekyll, and GitHub OAuth
Blog Posting and Markdown Web Editor
Building a Seamless Functional Blog Posting Pipeline Rube Goldberg machine with Hedgedoc, Jekyll, and GitHub OAuth
One of the goals for Scratchpad was simple:
let anyone on the team write a blog post without touching Git, YAML front‑matter, or terminal workflows—while keeping everything private and secure.
This post walks through how we built a full writing pipeline around Hedgedoc, Jekyll, OAuth2 Proxy, and a tiny publisher microservice that syncs posts into the blog automatically.
Why We Needed This
We wanted:
- A friendly, real‑time Markdown editor (Hedgedoc)
- Private access controlled through GitHub login
- Zero Git knowledge required from writers
- Automatic syncing → Jekyll
_posts/folder - A thin, maintainable backend without inventing a CMS
Hedgedoc checked most boxes, but we added authentication and a publishing workflow around it to make everything cohesive.
System Overview
Team Member → md.scratchpad.lol → OAuth2 Login → Hedgedoc
│
└── publisher microservice (8090)
│
└── writes markdown → project-blog/_posts/
│
└── Jekyll builds the site
Everything runs on a single VPS using Docker, Nginx, and LetsEncrypt SSL.
Authentication with GitHub + OAuth2 Proxy
Hedgedoc supports GitHub auth but doesn’t let us restrict who can log in.
That’s where oauth2-proxy comes in.
We configured oauth2-proxy to:
- use GitHub as the OAuth provider
- check a whitelist of approved emails (
/etc/oauth2-proxy/emails.txt) - serve our custom sign-in and error pages
- proxy requests to Hedgedoc internally
Custom templates live here:
project-blog/assets/auth-page/templates/
├── sign_in.html
└── error.html
Brand assets are served under:
scratchpad.lol/auth-assets/
The login screen now matches our Scratchpad branding with a clean, minimal UI.
Reverse Proxy Structure
We expose two subdomains:
| Purpose | Domain | Points To |
|---|---|---|
| Public blog | scratchpad.lol | Jekyll (port 4000) |
| Writer’s lab | md.scratchpad.lol | oauth2-proxy → Hedgedoc |
Nginx handles SSL, proxying, and static auth assets.
The Publisher Microservice
To avoid teaching every author how to commit Markdown into Jekyll, we wrote a small microservice using FastAPI.
Location:
project-blog/tools/publisher/
Responsibilities:
- Pull a note from Hedgedoc
- Convert it into valid Jekyll Markdown
- Inject required front‑matter
- Save it into
_posts/ - Commit changes to the repo (optional for automation)
Automatic Markdown Header Injection
Jekyll posts require YAML front‑matter like:
---
title: "My Post Title"
date: 2025-01-18 12:00:00
tags: []
---
The publisher service handles this automatically.
What the publisher does
- Takes the Hedgedoc title → becomes
title: - Generates
date: - Prepends YAML to the final Markdown file
Example output:
---
title: "Title From Hedgedoc"
date: 2025-01-18 12:00:00
tags: []
---
# Title From Hedgedoc
(rest of the Markdown)
Writers never touch YAML.
Publish / Unpublish Commands
Two global CLI helpers were added:
Publish
blog-publish <note-id>
Unpublish
blog-unpublish <note-id>
The note ID comes from the Hedgedoc URL:
https://md.scratchpad.lol/<note-id>
These commands hit the publisher API and sync the file automatically.
Directory Structure Recap
/root/scratchpad/
├── hedgedoc/ # Docker compose for Hedgedoc & OAuth
│ └── docker-compose.yml
├── project-blog/
│ ├── _posts/ # Auto-updated by publisher
│ ├── assets/auth-page/ # Branding + OAuth templates
│ └── tools/publisher/ # Sync microservice
└── nginx configs / SSL / etc…
The Result
The entire workflow is now seamless:
- Writer logs in via GitHub
- Opens Hedgedoc
- Writes
-
Runs:
blog-publish <note-id>
And the blog updates instantly—no friction, no YAML, no Git.
If you want alternative versions (diagram-heavy, onboarding-focused, public-friendly), I can generate those too.