Building a Seamless Writing Pipeline with Hedgedoc, Jekyll, and GitHub OAuth

infrascratchpaddevops

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:

  1. Pull a note from Hedgedoc
  2. Convert it into valid Jekyll Markdown
  3. Inject required front‑matter
  4. Save it into _posts/
  5. 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

  1. Takes the Hedgedoc title → becomes title:
  2. Generates date:
  3. 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:

  1. Writer logs in via GitHub
  2. Opens Hedgedoc
  3. Writes
  4. 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.