After The Unicorns Forced Me Into GitOps, my first priority was version control. But I didn’t want to use Github alone. As a home-labber I’m apparently allergic to trusting cloud services. Thus I landed on self-hosted Gitea.

Gitea Setup

I used docker compose to get it running using their docs. And after a docker compose up I could see the Gitea web UI at http://server-ip:3000 and finalized the installation there. Yet this didn’t magically start to track my files.

I needed a repo. After creating one via the web UI, I initialized a local one:

git init
git branch -M main
 
# to cache your credentials for future pushes
git config --global credential.helper store
 
# add private files like .env or .ssh_keys; also include postgres/
nano .gitignore 
 
git add .
git commit -m "Initial commit"
 
# fill in your details!!
git remote add origin http://your-username:GITEA_TOKEN@your-server-ip:3000/your-username/repo-name.git
 
git push -u origin main

Creating a Gitea token

Go to User Settings  Applications. Enter a “Token Name,” select Read/Write for repositories, and click Generate Token.

And that’s it! Almost… because now I could track my changes, yet I didn’t have a clear idea of what I was tracking. Creating documentation & runbooks have been in my mind for a while. And with version control in place, I had everything I needed to start.

Documentation Site

Lucky for me, Gitea was the last service I’ll have to rediscover the wheel. I created an On New Services runbook that used Gitea as the blueprint for what a typical service looked like — written in Markdown via Obsidian, of course. But I wanted a place to link them together and explore them visually (I have a thing for that). A Quartz 4 docs site is perfect for the job:

docs:
    build: ./docs
    container_name: docs
    environment:
      GIT_REPO:  "http://your-username:DOCS_GITEA_TOKEN@your-server-ip:3000/your-username/repo-name.git"
    networks:
      - gitea # the same internal docker network that gitea & postgres/SQLite are on
	ports:
	  - "80:80" # you may need a different host port if 80 is already taken
    volumes:
      - ./docs/quartz/content:/vault:ro
      - ./docs/quartz:/usr/src/app/quartz
    restart: unless-stopped

In your docs directory, you’ll need this Dockerfile to build from1:

FROM shommey/dockerized-quartz:latest
 
# Update to Node.js 22
RUN apt-get update && \
    apt-get install -y curl && \
    curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
    apt-get install -y nodejs && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* && \
    node --version && \
    npm --version
    
RUN git clone https://github.com/jackyzha0/quartz.git . && \  
	npm install

After creating another Git repo for the docs site, initialize the Quartz boilerplate:

# remove docs/ from the main repo tracking
nano .gitignore
 
# in /docs/quartz
git clone https://github.com/jackyzha0/quartz.git .
git branch -M main
 
# node must be >= v22; npm >= v10.9.2
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
 
# initialize Quartz repo
npm i
npx quartz create
 
git remote set-url origin http://your-username:GITEA_TOKEN@your-server-ip:3000/your-username/docs-repo-name.git 
 
git push -u origin main 

And after a build, you should see a template Quartz site at http://server-ip:80. But it doesn’t help if theres no documentation there… You can write markdown files at docs/vault/content — and if you’re patient, a manual restart will serve the files.

What if you’re not patient?

Then you’re in the same boat as me. We need CI/CD — lets create a gitea-runner that automatically rebuilds the docs site on repo push:

 gitea-runner:
    container_name: gitea-runner
    image: gitea/act_runner
    environment:
      - GITEA_INSTANCE_URL=http://gitea:3000
      - GITEA_RUNNER_REGISTRATION_TOKEN=PASTE_TOKEN_HERE
    networks:
      - gitea
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./gitea-runner/config.yaml:/config.yaml
    depends_on:
      - gitea

Registering a Gitea Runner

Go to Repository Settings  Actions  Runners. Copy and paste the Registration Token under Create new Runner.

In the gitea-runner directory, we need to generate a default config for job containers that the runner spawns:

docker run --rm --entrypoint="" gitea/act_runner act_runner generate-config > config.yaml

Then add this to the config so they can reach the gitea network:

container:
  network: "gitea"

With that set, we can create the workflow file at .gitea/workflows/deploy.yaml:

on:
  push:
    branches: [main] # when pushed to main
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: docker exec docs sh -c "cd /usr/src/app/quartz && git pull && npx quartz build --directory /vault --output /usr/share/nginx/html"
		# cd into the quartz dir --> pull changes --> build new site

Et voilà! After running docker compose up for the runner, any .md files pushed to docs/vault/content will appear on your docs site almost instantly.

Yet what do I do When It All Falls Down?

Footnotes

  1. Made possible by shommey’s dockerized-quartz