Published on

Publishing your personal knowledge management system using obsidian, quartz, private repositories, gitea, gitea actions and netlify


I've been using Obsidian for a while now, and after attending pkmsummit 2024 I was finally convinced to start publishing parts of my personal knowledge management (PKM) system.

Before we get started with my modestly complicated setup, lets start with the goals and requirements I had before publishing:

  • Goal: the goal of my publishing is to become a better writer by lowering barriers of publication, sharing more and receiving more feedback on my writing.
  • Privacy: My vault contains private notes that I never want to publish. The system should support mixed notes and linking without publishing the private notes.
  • Data sovereignty: I want my private notes to not leave my private homelab network other than on my own devices.
  • Ease of use: I want to use a single vault both private and public files, as I've found that switching between vaults increase the barrier to publish
  • Syncing: I want to be able to work on notes on all my devices, including my mobile phone.

So... this complicates things a little. If you are reading this and just want to get started quickly, these are the fastest options:

  • Obsidian sync: If you just want to publish immediately without any fuss use the obsidian sync service by obsidian. It works straight from the app, just fill in your creditcard and you can publish immediately.
  • Github Actions Quartz publish step: If you are ok publishing your whole vault to a github (private) repository, you can use the quartz publish step to publish to github pages. Nicole van der Hoeven created an excellent youtube video "how to publish your notes for free" explaining this exact setup.

Alright. So I don't want my private notes on github, even not in a private repository. That means I also cannot use the quartz github deployment flow and I have to come up with something more private on my own infrastructure. Before diving into the details, let's start with a component diagram to give a little overview of the solution:

Following the sequence of events:

  1. On my laptop and mobile phone, I create notes in Obsidian. On notes that I want to publish I set a frontmatter variable publish to true.
  2. When I'm ready to publish, I create a commit with all the changes to my vault since the last commit, including both private and the soon to be published notes.
  3. I connect my device to my home network using a VPN connection. Most modern routers support this feature nowadays, check brand of your router and google something like <brandname> <modelname> vpn guide.
  4. I push the commit to a private repository hosted with Gitea. Gitea is basically your own private open source github that runs smoothly on a raspberry pi.
  5. Gitea's "actions" feature detects the workflow action file in the notes repository and triggers a build and deploy step.
  6. The runner checks out the notes repository that contains both private and public notes.
  7. The runner checks out the quartz repository containing my configuration of the quartz setup and minor customisations.
  8. The runner builds the quartz website, filtering out all notes except the ones marked publish: true in the frontmatter.
  9. The runner uses the netlify-cli to deploy the site that was built to netlify using the site id and access token, and netlify then takes care of hosting the site with SSL on a custom domain.

And there we have it! Our PKM system is now (partly) public and we can start sharing our notes with the world. If you are interested in the end result, feel free to explore Peter's Mind Vault.

For each of the steps that require some configuration and code, I've shared mine:

Running gitea

for step 4, you need to run gitea and configure a git repository in gitea. The docker compose config for gitea looks a bit like:

version: '3.3'
    image: gitea/gitea:1.21.8
      - USER_UID=1000
      - USER_GID=1000
    restart: always
      - ~/gitea/:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
      - "3000:3000"
      - "222:22"
    container_name: gitea

Running gitea act runner

for step 5 to happen automatically, you need to run a separate gitea act runner and configure it as a runner for your gitea instance. The docker compose config for gitea looks a bit like:

    image: gitea/act_runner:nightly
      - ~/gitea-runner/data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    container_name: gitearunner

Gitea action workflow

After the gitea act runner is running and configured, you can create a .gitea/workflows/publish-quartz.yaml file in your notes repository. This workflow will be detected by gitea and automate the checkout of the notes, quartz site, build the site and publish it to npm:

name: Build Quartz Site
on: [push]
    runs-on: ubuntu-latest
      - name: Set up node
        uses: actions/setup-node@v4
          node-version: 20
      - name: Checkout peter-notes
        uses: actions/checkout@v3
          path: content
      - name: Check out petersmindvault quartz site
        run: |
          git clone
      - name: copy notes into quartz site
        run: |
          cp -r ${{ gitea.workspace }}/content/. ${{ gitea.workspace }}/petersmindvault/content
      - name: Get npm cache directory
        id: npm-cache-dir
        shell: bash
        run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT}
      - name: Activate NPM Cache
        id: npm-cache
        uses: actions/cache@v3
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          path: ${{ steps.npm-cache-dir.outputs.dir }}
          restore-keys: |
            ${{ runner.os }}-node-
      - name: npm install
        run: |
          cd ${{ gitea.workspace }}/petersmindvault
          npm i
      - name: build quartz site 
        run: |
          cd ${{ gitea.workspace }}/petersmindvault
          npx quartz build 
      - name: Publish with netlify
        uses: netlify/actions/cli@master
          args: deploy --prod --dir=${{ gitea.workspace }}/petersmindvault/public
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_API_TOKEN }}